1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * CZ.NIC's Turris Omnia LEDs driver 4 * 5 * 2020 by Marek Behun <marek.behun@nic.cz> 6 */ 7 8 #include <linux/i2c.h> 9 #include <linux/led-class-multicolor.h> 10 #include <linux/module.h> 11 #include <linux/mutex.h> 12 #include <linux/of.h> 13 #include "leds.h" 14 15 #define OMNIA_BOARD_LEDS 12 16 #define OMNIA_LED_NUM_CHANNELS 3 17 18 #define CMD_LED_MODE 3 19 #define CMD_LED_MODE_LED(l) ((l) & 0x0f) 20 #define CMD_LED_MODE_USER 0x10 21 22 #define CMD_LED_STATE 4 23 #define CMD_LED_STATE_LED(l) ((l) & 0x0f) 24 #define CMD_LED_STATE_ON 0x10 25 26 #define CMD_LED_COLOR 5 27 #define CMD_LED_SET_BRIGHTNESS 7 28 #define CMD_LED_GET_BRIGHTNESS 8 29 30 #define OMNIA_CMD 0 31 32 #define OMNIA_CMD_LED_COLOR_LED 1 33 #define OMNIA_CMD_LED_COLOR_R 2 34 #define OMNIA_CMD_LED_COLOR_G 3 35 #define OMNIA_CMD_LED_COLOR_B 4 36 #define OMNIA_CMD_LED_COLOR_LEN 5 37 38 struct omnia_led { 39 struct led_classdev_mc mc_cdev; 40 struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; 41 int reg; 42 }; 43 44 #define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev) 45 46 struct omnia_leds { 47 struct i2c_client *client; 48 struct mutex lock; 49 struct omnia_led leds[]; 50 }; 51 52 static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, 53 enum led_brightness brightness) 54 { 55 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); 56 struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); 57 struct omnia_led *led = to_omnia_led(mc_cdev); 58 u8 buf[OMNIA_CMD_LED_COLOR_LEN], state; 59 int ret; 60 61 mutex_lock(&leds->lock); 62 63 led_mc_calc_color_components(&led->mc_cdev, brightness); 64 65 buf[OMNIA_CMD] = CMD_LED_COLOR; 66 buf[OMNIA_CMD_LED_COLOR_LED] = led->reg; 67 buf[OMNIA_CMD_LED_COLOR_R] = mc_cdev->subled_info[0].brightness; 68 buf[OMNIA_CMD_LED_COLOR_G] = mc_cdev->subled_info[1].brightness; 69 buf[OMNIA_CMD_LED_COLOR_B] = mc_cdev->subled_info[2].brightness; 70 71 state = CMD_LED_STATE_LED(led->reg); 72 if (buf[OMNIA_CMD_LED_COLOR_R] || buf[OMNIA_CMD_LED_COLOR_G] || buf[OMNIA_CMD_LED_COLOR_B]) 73 state |= CMD_LED_STATE_ON; 74 75 ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state); 76 if (ret >= 0 && (state & CMD_LED_STATE_ON)) 77 ret = i2c_master_send(leds->client, buf, 5); 78 79 mutex_unlock(&leds->lock); 80 81 return ret; 82 } 83 84 static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, 85 struct device_node *np) 86 { 87 struct led_init_data init_data = {}; 88 struct device *dev = &client->dev; 89 struct led_classdev *cdev; 90 int ret, color; 91 92 ret = of_property_read_u32(np, "reg", &led->reg); 93 if (ret || led->reg >= OMNIA_BOARD_LEDS) { 94 dev_warn(dev, 95 "Node %pOF: must contain 'reg' property with values between 0 and %i\n", 96 np, OMNIA_BOARD_LEDS - 1); 97 return 0; 98 } 99 100 ret = of_property_read_u32(np, "color", &color); 101 if (ret || color != LED_COLOR_ID_MULTI) { 102 dev_warn(dev, 103 "Node %pOF: must contain 'color' property with value LED_COLOR_ID_MULTI\n", 104 np); 105 return 0; 106 } 107 108 led->subled_info[0].color_index = LED_COLOR_ID_RED; 109 led->subled_info[0].channel = 0; 110 led->subled_info[1].color_index = LED_COLOR_ID_GREEN; 111 led->subled_info[1].channel = 1; 112 led->subled_info[2].color_index = LED_COLOR_ID_BLUE; 113 led->subled_info[2].channel = 2; 114 115 led->mc_cdev.subled_info = led->subled_info; 116 led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; 117 118 init_data.fwnode = &np->fwnode; 119 120 cdev = &led->mc_cdev.led_cdev; 121 cdev->max_brightness = 255; 122 cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; 123 124 /* put the LED into software mode */ 125 ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, 126 CMD_LED_MODE_LED(led->reg) | 127 CMD_LED_MODE_USER); 128 if (ret < 0) { 129 dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, ret); 130 return ret; 131 } 132 133 /* disable the LED */ 134 ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, CMD_LED_STATE_LED(led->reg)); 135 if (ret < 0) { 136 dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); 137 return ret; 138 } 139 140 ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); 141 if (ret < 0) { 142 dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret); 143 return ret; 144 } 145 146 return 1; 147 } 148 149 /* 150 * On the front panel of the Turris Omnia router there is also a button which 151 * can be used to control the intensity of all the LEDs at once, so that if they 152 * are too bright, user can dim them. 153 * The microcontroller cycles between 8 levels of this global brightness (from 154 * 100% to 0%), but this setting can have any integer value between 0 and 100. 155 * It is therefore convenient to be able to change this setting from software. 156 * We expose this setting via a sysfs attribute file called "brightness". This 157 * file lives in the device directory of the LED controller, not an individual 158 * LED, so it should not confuse users. 159 */ 160 static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf) 161 { 162 struct i2c_client *client = to_i2c_client(dev); 163 struct omnia_leds *leds = i2c_get_clientdata(client); 164 int ret; 165 166 mutex_lock(&leds->lock); 167 ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); 168 mutex_unlock(&leds->lock); 169 170 if (ret < 0) 171 return ret; 172 173 return sprintf(buf, "%d\n", ret); 174 } 175 176 static ssize_t brightness_store(struct device *dev, struct device_attribute *a, const char *buf, 177 size_t count) 178 { 179 struct i2c_client *client = to_i2c_client(dev); 180 struct omnia_leds *leds = i2c_get_clientdata(client); 181 unsigned int brightness; 182 int ret; 183 184 if (sscanf(buf, "%u", &brightness) != 1) 185 return -EINVAL; 186 187 if (brightness > 100) 188 return -EINVAL; 189 190 mutex_lock(&leds->lock); 191 ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, (u8) brightness); 192 mutex_unlock(&leds->lock); 193 194 if (ret < 0) 195 return ret; 196 197 return count; 198 } 199 static DEVICE_ATTR_RW(brightness); 200 201 static struct attribute *omnia_led_controller_attrs[] = { 202 &dev_attr_brightness.attr, 203 NULL, 204 }; 205 ATTRIBUTE_GROUPS(omnia_led_controller); 206 207 static int omnia_leds_probe(struct i2c_client *client, 208 const struct i2c_device_id *id) 209 { 210 struct device *dev = &client->dev; 211 struct device_node *np = dev_of_node(dev), *child; 212 struct omnia_leds *leds; 213 struct omnia_led *led; 214 int ret, count; 215 216 count = of_get_available_child_count(np); 217 if (!count) { 218 dev_err(dev, "LEDs are not defined in device tree!\n"); 219 return -ENODEV; 220 } else if (count > OMNIA_BOARD_LEDS) { 221 dev_err(dev, "Too many LEDs defined in device tree!\n"); 222 return -EINVAL; 223 } 224 225 leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL); 226 if (!leds) 227 return -ENOMEM; 228 229 leds->client = client; 230 i2c_set_clientdata(client, leds); 231 232 mutex_init(&leds->lock); 233 234 led = &leds->leds[0]; 235 for_each_available_child_of_node(np, child) { 236 ret = omnia_led_register(client, led, child); 237 if (ret < 0) { 238 of_node_put(child); 239 return ret; 240 } 241 242 led += ret; 243 } 244 245 if (devm_device_add_groups(dev, omnia_led_controller_groups)) 246 dev_warn(dev, "Could not add attribute group!\n"); 247 248 return 0; 249 } 250 251 static int omnia_leds_remove(struct i2c_client *client) 252 { 253 u8 buf[OMNIA_CMD_LED_COLOR_LEN]; 254 255 /* put all LEDs into default (HW triggered) mode */ 256 i2c_smbus_write_byte_data(client, CMD_LED_MODE, 257 CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); 258 259 /* set all LEDs color to [255, 255, 255] */ 260 buf[OMNIA_CMD] = CMD_LED_COLOR; 261 buf[OMNIA_CMD_LED_COLOR_LED] = OMNIA_BOARD_LEDS; 262 buf[OMNIA_CMD_LED_COLOR_R] = 255; 263 buf[OMNIA_CMD_LED_COLOR_G] = 255; 264 buf[OMNIA_CMD_LED_COLOR_B] = 255; 265 266 i2c_master_send(client, buf, 5); 267 268 return 0; 269 } 270 271 static const struct of_device_id of_omnia_leds_match[] = { 272 { .compatible = "cznic,turris-omnia-leds", }, 273 {}, 274 }; 275 276 static const struct i2c_device_id omnia_id[] = { 277 { "omnia", 0 }, 278 { } 279 }; 280 281 static struct i2c_driver omnia_leds_driver = { 282 .probe = omnia_leds_probe, 283 .remove = omnia_leds_remove, 284 .id_table = omnia_id, 285 .driver = { 286 .name = "leds-turris-omnia", 287 .of_match_table = of_omnia_leds_match, 288 }, 289 }; 290 291 module_i2c_driver(omnia_leds_driver); 292 293 MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>"); 294 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs"); 295 MODULE_LICENSE("GPL v2"); 296