Commit | Line | Data |
---|---|---|
2b27bdcc | 1 | // SPDX-License-Identifier: GPL-2.0-only |
500fe141 SO |
2 | /* |
3 | * LP5521 LED chip driver. | |
4 | * | |
5 | * Copyright (C) 2010 Nokia Corporation | |
a2387cb9 | 6 | * Copyright (C) 2012 Texas Instruments |
500fe141 SO |
7 | * |
8 | * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> | |
a2387cb9 | 9 | * Milo(Woogyom) Kim <milo.kim@ti.com> |
500fe141 SO |
10 | */ |
11 | ||
4137d94f | 12 | #include <linux/cleanup.h> |
500fe141 | 13 | #include <linux/delay.h> |
79bcc10b MWK |
14 | #include <linux/firmware.h> |
15 | #include <linux/i2c.h> | |
500fe141 | 16 | #include <linux/leds.h> |
79bcc10b MWK |
17 | #include <linux/module.h> |
18 | #include <linux/mutex.h> | |
6a0c9a47 | 19 | #include <linux/platform_data/leds-lp55xx.h> |
79bcc10b | 20 | #include <linux/slab.h> |
7542a04b | 21 | #include <linux/of.h> |
6a0c9a47 MWK |
22 | |
23 | #include "leds-lp55xx-common.h" | |
500fe141 | 24 | |
12f022d2 MWK |
25 | #define LP5521_MAX_LEDS 3 |
26 | #define LP5521_CMD_DIRECT 0x3F | |
500fe141 SO |
27 | |
28 | /* Registers */ | |
29 | #define LP5521_REG_ENABLE 0x00 | |
30 | #define LP5521_REG_OP_MODE 0x01 | |
31 | #define LP5521_REG_R_PWM 0x02 | |
32 | #define LP5521_REG_G_PWM 0x03 | |
33 | #define LP5521_REG_B_PWM 0x04 | |
34 | #define LP5521_REG_R_CURRENT 0x05 | |
35 | #define LP5521_REG_G_CURRENT 0x06 | |
36 | #define LP5521_REG_B_CURRENT 0x07 | |
37 | #define LP5521_REG_CONFIG 0x08 | |
500fe141 SO |
38 | #define LP5521_REG_STATUS 0x0C |
39 | #define LP5521_REG_RESET 0x0D | |
500fe141 SO |
40 | #define LP5521_REG_R_PROG_MEM 0x10 |
41 | #define LP5521_REG_G_PROG_MEM 0x30 | |
42 | #define LP5521_REG_B_PROG_MEM 0x50 | |
43 | ||
500fe141 SO |
44 | /* Base register to set LED current */ |
45 | #define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT | |
500fe141 SO |
46 | /* Base register to set the brightness */ |
47 | #define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM | |
48 | ||
49 | /* Bits in ENABLE register */ | |
50 | #define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ | |
51 | #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ | |
52 | #define LP5521_EXEC_RUN 0x2A | |
32a2f747 KM |
53 | #define LP5521_ENABLE_DEFAULT \ |
54 | (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) | |
55 | #define LP5521_ENABLE_RUN_PROGRAM \ | |
56 | (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) | |
500fe141 | 57 | |
81f2a5b4 KM |
58 | /* CONFIG register */ |
59 | #define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ | |
60 | #define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ | |
54a7bef5 MZ |
61 | #define LP5521_CP_MODE_MASK 0x18 /* Charge pump mode */ |
62 | #define LP5521_CP_MODE_SHIFT 3 | |
81f2a5b4 KM |
63 | #define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ |
64 | #define LP5521_CLK_INT 0x01 /* Internal clock */ | |
54a7bef5 | 65 | #define LP5521_DEFAULT_CFG (LP5521_PWM_HF | LP5521_PWRSAVE_EN) |
81f2a5b4 | 66 | |
500fe141 SO |
67 | /* Status */ |
68 | #define LP5521_EXT_CLK_USED 0x08 | |
69 | ||
b3c49c05 SK |
70 | /* default R channel current register value */ |
71 | #define LP5521_REG_R_CURR_DEFAULT 0xAF | |
72 | ||
48068d5d MWK |
73 | /* Reset register value */ |
74 | #define LP5521_RESET 0xFF | |
75 | ||
9ce7cb17 MWK |
76 | static inline void lp5521_wait_opmode_done(void) |
77 | { | |
78 | /* operation mode change needs to be longer than 153 us */ | |
79 | usleep_range(200, 300); | |
80 | } | |
81 | ||
94482174 MWK |
82 | static inline void lp5521_wait_enable_done(void) |
83 | { | |
84 | /* it takes more 488 us to update ENABLE register */ | |
85 | usleep_range(500, 600); | |
86 | } | |
87 | ||
9ce7cb17 | 88 | static void lp5521_run_engine(struct lp55xx_chip *chip, bool start) |
500fe141 | 89 | { |
500fe141 | 90 | int ret; |
500fe141 | 91 | |
9ce7cb17 MWK |
92 | /* stop engine */ |
93 | if (!start) { | |
43e91e5e | 94 | lp55xx_stop_engine(chip); |
9ce7cb17 MWK |
95 | lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); |
96 | lp5521_wait_opmode_done(); | |
97 | return; | |
98 | } | |
99 | ||
42a9eaac CM |
100 | ret = lp55xx_run_engine_common(chip); |
101 | if (!ret) | |
102 | lp5521_wait_enable_done(); | |
9ce7cb17 MWK |
103 | } |
104 | ||
ffbdccdb | 105 | static int lp5521_post_init_device(struct lp55xx_chip *chip) |
500fe141 | 106 | { |
500fe141 | 107 | int ret; |
94482174 | 108 | u8 val; |
500fe141 | 109 | |
94482174 MWK |
110 | /* |
111 | * Make sure that the chip is reset by reading back the r channel | |
112 | * current reg. This is dummy read is required on some platforms - | |
113 | * otherwise further access to the R G B channels in the | |
114 | * LP5521_REG_ENABLE register will not have any effect - strange! | |
115 | */ | |
ffbdccdb | 116 | ret = lp55xx_read(chip, LP5521_REG_R_CURRENT, &val); |
94482174 | 117 | if (ret) { |
ffbdccdb | 118 | dev_err(&chip->cl->dev, "error in resetting chip\n"); |
94482174 MWK |
119 | return ret; |
120 | } | |
121 | if (val != LP5521_REG_R_CURR_DEFAULT) { | |
ffbdccdb | 122 | dev_err(&chip->cl->dev, |
94482174 MWK |
123 | "unexpected data in register (expected 0x%x got 0x%x)\n", |
124 | LP5521_REG_R_CURR_DEFAULT, val); | |
125 | ret = -EINVAL; | |
126 | return ret; | |
127 | } | |
128 | usleep_range(10000, 20000); | |
500fe141 | 129 | |
500fe141 | 130 | /* Set all PWMs to direct control mode */ |
ffbdccdb | 131 | ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); |
b9604be2 SH |
132 | if (ret) |
133 | return ret; | |
500fe141 | 134 | |
81f2a5b4 KM |
135 | /* Update configuration for the clock setting */ |
136 | val = LP5521_DEFAULT_CFG; | |
137 | if (!lp55xx_is_extclk_used(chip)) | |
138 | val |= LP5521_CLK_INT; | |
139 | ||
54a7bef5 MZ |
140 | val |= (chip->pdata->charge_pump_mode << LP5521_CP_MODE_SHIFT) & LP5521_CP_MODE_MASK; |
141 | ||
ffbdccdb | 142 | ret = lp55xx_write(chip, LP5521_REG_CONFIG, val); |
94482174 MWK |
143 | if (ret) |
144 | return ret; | |
500fe141 SO |
145 | |
146 | /* Initialize all channels PWM to zero -> leds off */ | |
ffbdccdb MWK |
147 | lp55xx_write(chip, LP5521_REG_R_PWM, 0); |
148 | lp55xx_write(chip, LP5521_REG_G_PWM, 0); | |
149 | lp55xx_write(chip, LP5521_REG_B_PWM, 0); | |
500fe141 SO |
150 | |
151 | /* Set engines are set to run state when OP_MODE enables engines */ | |
ffbdccdb | 152 | ret = lp55xx_write(chip, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); |
94482174 MWK |
153 | if (ret) |
154 | return ret; | |
500fe141 | 155 | |
94482174 MWK |
156 | lp5521_wait_enable_done(); |
157 | ||
158 | return 0; | |
500fe141 SO |
159 | } |
160 | ||
9ca3bd80 | 161 | static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf) |
500fe141 | 162 | { |
9ca3bd80 | 163 | struct lp55xx_platform_data *pdata = chip->pdata; |
500fe141 SO |
164 | int ret; |
165 | u8 status; | |
166 | ||
9ca3bd80 | 167 | ret = lp55xx_read(chip, LP5521_REG_STATUS, &status); |
500fe141 SO |
168 | if (ret < 0) |
169 | return ret; | |
170 | ||
9ca3bd80 MWK |
171 | if (pdata->clock_mode != LP55XX_CLOCK_EXT) |
172 | return 0; | |
173 | ||
500fe141 | 174 | /* Check that ext clock is really in use if requested */ |
9ca3bd80 MWK |
175 | if ((status & LP5521_EXT_CLK_USED) == 0) |
176 | return -EIO; | |
177 | ||
500fe141 SO |
178 | return 0; |
179 | } | |
180 | ||
500fe141 SO |
181 | static ssize_t lp5521_selftest(struct device *dev, |
182 | struct device_attribute *attr, | |
183 | char *buf) | |
184 | { | |
9ca3bd80 MWK |
185 | struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); |
186 | struct lp55xx_chip *chip = led->chip; | |
500fe141 SO |
187 | int ret; |
188 | ||
4137d94f CM |
189 | guard(mutex)(&chip->lock); |
190 | ||
500fe141 | 191 | ret = lp5521_run_selftest(chip, buf); |
24d32128 | 192 | |
3f6fb1cf | 193 | return sysfs_emit(buf, "%s\n", ret ? "FAIL" : "OK"); |
500fe141 SO |
194 | } |
195 | ||
500fe141 | 196 | /* device attributes */ |
082a4d3f CM |
197 | LP55XX_DEV_ATTR_ENGINE_MODE(1); |
198 | LP55XX_DEV_ATTR_ENGINE_MODE(2); | |
199 | LP55XX_DEV_ATTR_ENGINE_MODE(3); | |
200 | LP55XX_DEV_ATTR_ENGINE_LOAD(1); | |
201 | LP55XX_DEV_ATTR_ENGINE_LOAD(2); | |
202 | LP55XX_DEV_ATTR_ENGINE_LOAD(3); | |
c0e5e9b5 | 203 | static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest); |
500fe141 SO |
204 | |
205 | static struct attribute *lp5521_attributes[] = { | |
c0e5e9b5 MK |
206 | &dev_attr_engine1_mode.attr, |
207 | &dev_attr_engine2_mode.attr, | |
208 | &dev_attr_engine3_mode.attr, | |
209 | &dev_attr_engine1_load.attr, | |
210 | &dev_attr_engine2_load.attr, | |
211 | &dev_attr_engine3_load.attr, | |
500fe141 | 212 | &dev_attr_selftest.attr, |
500fe141 SO |
213 | NULL |
214 | }; | |
215 | ||
216 | static const struct attribute_group lp5521_group = { | |
217 | .attrs = lp5521_attributes, | |
218 | }; | |
219 | ||
48068d5d MWK |
220 | /* Chip specific configurations */ |
221 | static struct lp55xx_device_config lp5521_cfg = { | |
a9b202b9 CM |
222 | .reg_op_mode = { |
223 | .addr = LP5521_REG_OP_MODE, | |
224 | }, | |
42a9eaac CM |
225 | .reg_exec = { |
226 | .addr = LP5521_REG_ENABLE, | |
227 | }, | |
48068d5d MWK |
228 | .reset = { |
229 | .addr = LP5521_REG_RESET, | |
230 | .val = LP5521_RESET, | |
231 | }, | |
e3a700d8 MWK |
232 | .enable = { |
233 | .addr = LP5521_REG_ENABLE, | |
234 | .val = LP5521_ENABLE_DEFAULT, | |
235 | }, | |
31379a57 CM |
236 | .prog_mem_base = { |
237 | .addr = LP5521_REG_R_PROG_MEM, | |
238 | }, | |
c63580b2 CM |
239 | .reg_led_pwm_base = { |
240 | .addr = LP5521_REG_LED_PWM_BASE, | |
241 | }, | |
01e0290d CM |
242 | .reg_led_current_base = { |
243 | .addr = LP5521_REG_LED_CURRENT_BASE, | |
244 | }, | |
0e202346 | 245 | .max_channel = LP5521_MAX_LEDS, |
ffbdccdb | 246 | .post_init_device = lp5521_post_init_device, |
c63580b2 | 247 | .brightness_fn = lp55xx_led_brightness, |
794826b2 | 248 | .multicolor_brightness_fn = lp55xx_multicolor_brightness, |
01e0290d | 249 | .set_led_current = lp55xx_set_led_current, |
a3df1906 | 250 | .firmware_cb = lp55xx_firmware_loaded_cb, |
9ce7cb17 | 251 | .run_engine = lp5521_run_engine, |
e73c0ce6 | 252 | .dev_attr_group = &lp5521_group, |
48068d5d MWK |
253 | }; |
254 | ||
500fe141 | 255 | static const struct i2c_device_id lp5521_id[] = { |
db30c289 | 256 | { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */ |
500fe141 SO |
257 | { } |
258 | }; | |
259 | MODULE_DEVICE_TABLE(i2c, lp5521_id); | |
260 | ||
b548a34b | 261 | static const struct of_device_id of_lp5521_leds_match[] = { |
db30c289 | 262 | { .compatible = "national,lp5521", .data = &lp5521_cfg, }, |
b548a34b AL |
263 | {}, |
264 | }; | |
265 | ||
266 | MODULE_DEVICE_TABLE(of, of_lp5521_leds_match); | |
3d590af8 | 267 | |
500fe141 SO |
268 | static struct i2c_driver lp5521_driver = { |
269 | .driver = { | |
270 | .name = "lp5521", | |
3d590af8 | 271 | .of_match_table = of_lp5521_leds_match, |
500fe141 | 272 | }, |
db30c289 CM |
273 | .probe = lp55xx_probe, |
274 | .remove = lp55xx_remove, | |
500fe141 SO |
275 | .id_table = lp5521_id, |
276 | }; | |
277 | ||
09a0d183 | 278 | module_i2c_driver(lp5521_driver); |
500fe141 SO |
279 | |
280 | MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo"); | |
a2387cb9 | 281 | MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); |
500fe141 SO |
282 | MODULE_DESCRIPTION("LP5521 LED engine"); |
283 | MODULE_LICENSE("GPL v2"); |