1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LED driver for PCA995x I2C LED drivers |
4 | * |
5 | * Copyright 2011 bct electronic GmbH |
6 | * Copyright 2013 Qtechnology/AS |
7 | * Copyright 2022 NXP |
8 | * Copyright 2023 Marek Vasut |
9 | */ |
10 | |
11 | #include <linux/i2c.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/property.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | /* Register definition */ |
19 | #define PCA995X_MODE1 0x00 |
20 | #define PCA995X_MODE2 0x01 |
21 | #define PCA995X_LEDOUT0 0x02 |
22 | #define PCA9955B_PWM0 0x08 |
23 | #define PCA9952_PWM0 0x0A |
24 | #define PCA9952_IREFALL 0x43 |
25 | #define PCA9955B_IREFALL 0x45 |
26 | |
27 | /* Auto-increment disabled. Normal mode */ |
28 | #define PCA995X_MODE1_CFG 0x00 |
29 | |
30 | /* LED select registers determine the source that drives LED outputs */ |
31 | #define PCA995X_LED_OFF 0x0 |
32 | #define PCA995X_LED_ON 0x1 |
33 | #define PCA995X_LED_PWM_MODE 0x2 |
34 | #define PCA995X_LDRX_MASK 0x3 |
35 | #define PCA995X_LDRX_BITS 2 |
36 | |
37 | #define PCA995X_MAX_OUTPUTS 16 |
38 | #define PCA995X_OUTPUTS_PER_REG 4 |
39 | |
40 | #define PCA995X_IREFALL_FULL_CFG 0xFF |
41 | #define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) |
42 | |
43 | #define PCA995X_TYPE_NON_B 0 |
44 | #define PCA995X_TYPE_B 1 |
45 | |
46 | #define ldev_to_led(c) container_of(c, struct pca995x_led, ldev) |
47 | |
48 | struct pca995x_led { |
49 | unsigned int led_no; |
50 | struct led_classdev ldev; |
51 | struct pca995x_chip *chip; |
52 | }; |
53 | |
54 | struct pca995x_chip { |
55 | struct regmap *regmap; |
56 | struct pca995x_led leds[PCA995X_MAX_OUTPUTS]; |
57 | int btype; |
58 | }; |
59 | |
60 | static int pca995x_brightness_set(struct led_classdev *led_cdev, |
61 | enum led_brightness brightness) |
62 | { |
63 | struct pca995x_led *led = ldev_to_led(led_cdev); |
64 | struct pca995x_chip *chip = led->chip; |
65 | u8 ledout_addr, pwmout_addr; |
66 | int shift, ret; |
67 | |
68 | pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no; |
69 | ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG); |
70 | shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG); |
71 | |
72 | switch (brightness) { |
73 | case LED_FULL: |
74 | return regmap_update_bits(map: chip->regmap, reg: ledout_addr, |
75 | PCA995X_LDRX_MASK << shift, |
76 | PCA995X_LED_ON << shift); |
77 | case LED_OFF: |
78 | return regmap_update_bits(map: chip->regmap, reg: ledout_addr, |
79 | PCA995X_LDRX_MASK << shift, val: 0); |
80 | default: |
81 | /* Adjust brightness as per user input by changing individual PWM */ |
82 | ret = regmap_write(map: chip->regmap, reg: pwmout_addr, val: brightness); |
83 | if (ret) |
84 | return ret; |
85 | |
86 | /* |
87 | * Change LDRx configuration to individual brightness via PWM. |
88 | * LED will stop blinking if it's doing so. |
89 | */ |
90 | return regmap_update_bits(map: chip->regmap, reg: ledout_addr, |
91 | PCA995X_LDRX_MASK << shift, |
92 | PCA995X_LED_PWM_MODE << shift); |
93 | } |
94 | } |
95 | |
96 | static const struct regmap_config pca995x_regmap = { |
97 | .reg_bits = 8, |
98 | .val_bits = 8, |
99 | .max_register = 0x49, |
100 | }; |
101 | |
102 | static int pca995x_probe(struct i2c_client *client) |
103 | { |
104 | struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 }; |
105 | struct fwnode_handle *np, *child; |
106 | struct device *dev = &client->dev; |
107 | struct pca995x_chip *chip; |
108 | struct pca995x_led *led; |
109 | int i, btype, reg, ret; |
110 | |
111 | btype = (unsigned long)device_get_match_data(dev: &client->dev); |
112 | |
113 | np = dev_fwnode(dev); |
114 | if (!np) |
115 | return -ENODEV; |
116 | |
117 | chip = devm_kzalloc(dev, size: sizeof(*chip), GFP_KERNEL); |
118 | if (!chip) |
119 | return -ENOMEM; |
120 | |
121 | chip->btype = btype; |
122 | chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap); |
123 | if (IS_ERR(ptr: chip->regmap)) |
124 | return PTR_ERR(ptr: chip->regmap); |
125 | |
126 | i2c_set_clientdata(client, data: chip); |
127 | |
128 | fwnode_for_each_available_child_node(np, child) { |
129 | ret = fwnode_property_read_u32(fwnode: child, propname: "reg" , val: ®); |
130 | if (ret) { |
131 | fwnode_handle_put(fwnode: child); |
132 | return ret; |
133 | } |
134 | |
135 | if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) { |
136 | fwnode_handle_put(fwnode: child); |
137 | return -EINVAL; |
138 | } |
139 | |
140 | led = &chip->leds[reg]; |
141 | led_fwnodes[reg] = child; |
142 | led->chip = chip; |
143 | led->led_no = reg; |
144 | led->ldev.brightness_set_blocking = pca995x_brightness_set; |
145 | led->ldev.max_brightness = 255; |
146 | } |
147 | |
148 | for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) { |
149 | struct led_init_data init_data = {}; |
150 | |
151 | if (!led_fwnodes[i]) |
152 | continue; |
153 | |
154 | init_data.fwnode = led_fwnodes[i]; |
155 | |
156 | ret = devm_led_classdev_register_ext(parent: dev, |
157 | led_cdev: &chip->leds[i].ldev, |
158 | init_data: &init_data); |
159 | if (ret < 0) { |
160 | fwnode_handle_put(fwnode: child); |
161 | return dev_err_probe(dev, err: ret, |
162 | fmt: "Could not register LED %s\n" , |
163 | chip->leds[i].ldev.name); |
164 | } |
165 | } |
166 | |
167 | /* Disable LED all-call address and set normal mode */ |
168 | ret = regmap_write(map: chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG); |
169 | if (ret) |
170 | return ret; |
171 | |
172 | /* IREF Output current value for all LEDn outputs */ |
173 | return regmap_write(map: chip->regmap, |
174 | reg: btype ? PCA9955B_IREFALL : PCA9952_IREFALL, |
175 | PCA995X_IREFALL_HALF_CFG); |
176 | } |
177 | |
178 | static const struct i2c_device_id pca995x_id[] = { |
179 | { "pca9952" , .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B }, |
180 | { "pca9955b" , .driver_data = (kernel_ulong_t)PCA995X_TYPE_B }, |
181 | {} |
182 | }; |
183 | MODULE_DEVICE_TABLE(i2c, pca995x_id); |
184 | |
185 | static const struct of_device_id pca995x_of_match[] = { |
186 | { .compatible = "nxp,pca9952" , .data = (void *)PCA995X_TYPE_NON_B }, |
187 | { .compatible = "nxp,pca9955b" , .data = (void *)PCA995X_TYPE_B }, |
188 | {}, |
189 | }; |
190 | MODULE_DEVICE_TABLE(of, pca995x_of_match); |
191 | |
192 | static struct i2c_driver pca995x_driver = { |
193 | .driver = { |
194 | .name = "leds-pca995x" , |
195 | .of_match_table = pca995x_of_match, |
196 | }, |
197 | .probe = pca995x_probe, |
198 | .id_table = pca995x_id, |
199 | }; |
200 | module_i2c_driver(pca995x_driver); |
201 | |
202 | MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>" ); |
203 | MODULE_DESCRIPTION("PCA995x LED driver" ); |
204 | MODULE_LICENSE("GPL" ); |
205 | |