package: kernel: leds-gca230718: fix build with Linux 6.6
[openwrt/openwrt.git] / package / kernel / leds-gca230718 / src / leds-gca230718.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * GCA230718 LED support (e.g. for D-Link M30) using I2C
4 *
5 * Copyright 2022 Roland Reinl <reinlroland+github@gmail.com>
6 *
7 * This driver can control RGBW LEDs which are connected to a GCA230718.
8 */
9
10 #include <linux/delay.h>
11 #include <linux/led-class-multicolor.h>
12 #include <linux/leds.h>
13 #include <linux/module.h>
14 #include <linux/of_device.h>
15 #include <linux/property.h>
16 #include <linux/i2c.h>
17 #include <linux/mutex.h>
18 #include <linux/version.h>
19
20 #define GCA230718_MAX_LEDS (4u)
21
22 #define GCA230718_OPMODE_DISABLED (0x00u)
23 #define GCA230718_OPMODE_NO_TOGGLE (0x01u)
24 #define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_DISABLED (0x02u)
25 #define GCA230718_OPMODE_TOGGLE_RAMP_CONTROL_ENSABLED (0x03u)
26
27 #define GCA230718_1ST_SEQUENCE_BYTE_1 (0x02u)
28 #define GCA230718_2ND_SEQUENCE_BYTE_1 (0x01u)
29 #define GCA230718_3RD_SEQUENCE_BYTE_1 (0x03u)
30
31 struct gca230718_led
32 {
33 enum led_brightness brightness;
34 struct i2c_client *client;
35 struct led_classdev ledClassDev;
36 };
37
38 struct gca230718_private
39 {
40 struct mutex lock;
41 struct gca230718_led leds[GCA230718_MAX_LEDS];
42 };
43
44 static void gca230718_init_private_led_data(struct gca230718_private* data)
45 {
46 u8 ledIndex;
47 for (ledIndex = 0; ledIndex < GCA230718_MAX_LEDS; ledIndex++)
48 {
49 data->leds[ledIndex].client = NULL;
50 }
51 }
52
53 static void gca230718_send_sequence(struct i2c_client *client, u8 byte0, struct gca230718_private* gca230718_privateData)
54 {
55 int status = 0;
56 u8 ledIndex;
57 const u8 resetCommand[2] = { 0x81, 0xE4 };
58 const u8 resetCommandRegister = 0x00;
59
60 u8 controlCommand[13];
61 const u8 controlCommandRegister = 0x03;
62
63 controlCommand[0] = 0x0C; /* Unknown */
64 controlCommand[1] = byte0;
65 controlCommand[2] = GCA230718_OPMODE_NO_TOGGLE;
66 /* Byte 3-6 are set below to the brighness value of the individual LEDs */
67 controlCommand[7] = 0x01; /* Frequency, doesn't care as long as GCA230718_OPMODE_NO_TOGGLE is used above */
68 /* Byte 8-11 are set below to the brighness value of the individual LEDs */
69 controlCommand[12] = 0x87;
70
71 for (ledIndex = 0; ledIndex < GCA230718_MAX_LEDS; ledIndex++)
72 {
73 controlCommand[3 + ledIndex] = gca230718_privateData->leds[ledIndex].brightness;
74 controlCommand[8 + ledIndex] = gca230718_privateData->leds[ledIndex].brightness;
75 }
76
77 mutex_lock(&(gca230718_privateData->lock));
78
79 if ((status = i2c_smbus_write_i2c_block_data(client, resetCommandRegister, sizeof(resetCommand), resetCommand)) != 0)
80 {
81 pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for reset command\n", status);
82 }
83 else if ((status = i2c_smbus_write_i2c_block_data(client, controlCommandRegister, sizeof(controlCommand), controlCommand)) != 0)
84 {
85 pr_info("Error %i during call of i2c_smbus_write_i2c_block_data for control command\n", status);
86 }
87
88 mutex_unlock(&(gca230718_privateData->lock));
89 }
90
91 static int gca230718_set_brightness(struct led_classdev *led_cdev, enum led_brightness value)
92 {
93 struct gca230718_led* led;
94 struct i2c_client* client;
95
96 led = container_of(led_cdev, struct gca230718_led, ledClassDev);
97 client = led->client;
98
99 if (client != NULL)
100 {
101 struct gca230718_private* gca230718_privateData;
102
103 led->brightness = value;
104 gca230718_privateData = i2c_get_clientdata(client);
105
106 gca230718_send_sequence(client, GCA230718_2ND_SEQUENCE_BYTE_1, gca230718_privateData);
107 }
108
109 return 0;
110 }
111
112 #if LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0)
113 static int gca230718_probe(struct i2c_client *client)
114 #else
115 static int gca230718_probe(struct i2c_client *client, const struct i2c_device_id *id)
116 #endif
117 {
118 int status = 0;
119 struct gca230718_private* gca230718_privateData;
120
121 pr_info("Enter gca230718_probe for device address %u\n", client->addr);
122 gca230718_privateData = devm_kzalloc(&(client->dev), sizeof(struct gca230718_private), GFP_KERNEL);
123
124 if (gca230718_privateData == NULL)
125 {
126 pr_info("Error during allocating memory for private data\n");
127 status = -ENOMEM;
128 }
129 else
130 {
131 struct device_node* ledNode;
132 mutex_init(&gca230718_privateData->lock);
133 gca230718_init_private_led_data(gca230718_privateData);
134 i2c_set_clientdata(client, gca230718_privateData);
135
136 for_each_child_of_node(client->dev.of_node, ledNode)
137 {
138 u32 regValue = 0;
139 if (of_property_read_u32(ledNode, "reg", &regValue) != 0)
140 {
141 pr_info("Missing entry \"reg\" in node %s\n", ledNode->name);
142 }
143 else if (regValue >= GCA230718_MAX_LEDS)
144 {
145 pr_info("Invalid entry \"reg\" in node %s (%u)\n", ledNode->name, regValue);
146 }
147 else
148 {
149 struct led_classdev* ledClassDev = &(gca230718_privateData->leds[regValue].ledClassDev);
150 struct led_init_data init_data = {};
151
152 gca230718_privateData->leds[regValue].client = client;
153 init_data.fwnode = of_fwnode_handle(ledNode);
154
155 pr_info("Creating LED for node %s: reg=%u\n", ledNode->name, regValue);
156
157 ledClassDev->name = of_get_property(ledNode, "label", NULL);
158 if (ledClassDev->name == NULL)
159 {
160 ledClassDev->name = ledNode->name;
161 }
162
163 ledClassDev->brightness = LED_OFF;
164 ledClassDev->max_brightness = LED_FULL;
165 ledClassDev->brightness_set_blocking = gca230718_set_brightness;
166
167 if (devm_led_classdev_register_ext(&(client->dev), ledClassDev, &init_data) != 0)
168 {
169 pr_info("Error during call of devm_led_classdev_register_ext");
170 }
171 }
172 }
173 }
174
175 if (status == 0)
176 {
177 /*
178 Send full initialization sequence.
179 Afterwards only GCA230718_2ND_SEQUENCE_BYTE_1 must be send to upddate the brightness values.
180 */
181 gca230718_send_sequence(client, GCA230718_1ST_SEQUENCE_BYTE_1, gca230718_privateData);
182 gca230718_send_sequence(client, GCA230718_2ND_SEQUENCE_BYTE_1, gca230718_privateData);
183 gca230718_send_sequence(client, GCA230718_3RD_SEQUENCE_BYTE_1, gca230718_privateData);
184 }
185
186 return status;
187 }
188
189 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,18,0)
190 static void gca230718_remove(struct i2c_client *client)
191 #else
192 static int gca230718_remove(struct i2c_client *client)
193 #endif
194 {
195 struct gca230718_private* gca230718_privateData;
196 gca230718_privateData = i2c_get_clientdata(client);
197 mutex_destroy(&gca230718_privateData->lock);
198 gca230718_init_private_led_data(gca230718_privateData);
199
200 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0)
201 return 0;
202 #endif
203 }
204
205 static const struct i2c_device_id gca230718_i2c_ids[] = {
206 { "gca230718", 0 },
207 {},
208 };
209 MODULE_DEVICE_TABLE(i2c, gca230718_i2c_ids);
210
211 static const struct of_device_id gca230718_dt_ids[] = {
212 { .compatible = "unknown,gca230718" },
213 {},
214 };
215 MODULE_DEVICE_TABLE(of, gca230718_dt_ids);
216
217 static struct i2c_driver gca230718_driver = {
218 .probe = gca230718_probe,
219 .remove = gca230718_remove,
220 .id_table = gca230718_i2c_ids,
221 .driver = {
222 .name = KBUILD_MODNAME,
223 .of_match_table = gca230718_dt_ids,
224 },
225 };
226
227 module_i2c_driver(gca230718_driver);
228
229 MODULE_AUTHOR("Roland Reinl <reinlroland+github@gmail.com>");
230 MODULE_DESCRIPTION("GCA230718 LED support (e.g. for D-Link M30) using I2C");
231 MODULE_LICENSE("GPL");