1 /* 2 * LEDs driver for Freescale MC13783/MC13892 3 * 4 * Copyright (C) 2010 Philippe Rétornaz 5 * 6 * Based on leds-da903x: 7 * Copyright (C) 2008 Compulab, Ltd. 8 * Mike Rapoport <mike@compulab.co.il> 9 * 10 * Copyright (C) 2006-2008 Marvell International Ltd. 11 * Eric Miao <eric.miao@marvell.com> 12 * 13 * This program is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU General Public License version 2 as 15 * published by the Free Software Foundation. 16 */ 17 18 #include <linux/module.h> 19 #include <linux/kernel.h> 20 #include <linux/init.h> 21 #include <linux/platform_device.h> 22 #include <linux/leds.h> 23 #include <linux/workqueue.h> 24 #include <linux/mfd/mc13xxx.h> 25 26 #define MC13XXX_REG_LED_CONTROL(x) (51 + (x)) 27 28 struct mc13xxx_led_devtype { 29 int led_min; 30 int led_max; 31 int num_regs; 32 }; 33 34 struct mc13xxx_led { 35 struct led_classdev cdev; 36 struct work_struct work; 37 struct mc13xxx *master; 38 enum led_brightness new_brightness; 39 int id; 40 }; 41 42 struct mc13xxx_leds { 43 struct mc13xxx_led_devtype *devtype; 44 int num_leds; 45 struct mc13xxx_led led[0]; 46 }; 47 48 static void mc13xxx_led_work(struct work_struct *work) 49 { 50 struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work); 51 int reg, mask, value, bank, off, shift; 52 53 switch (led->id) { 54 case MC13783_LED_MD: 55 reg = MC13XXX_REG_LED_CONTROL(2); 56 shift = 9; 57 mask = 0x0f; 58 value = led->new_brightness >> 4; 59 break; 60 case MC13783_LED_AD: 61 reg = MC13XXX_REG_LED_CONTROL(2); 62 shift = 13; 63 mask = 0x0f; 64 value = led->new_brightness >> 4; 65 break; 66 case MC13783_LED_KP: 67 reg = MC13XXX_REG_LED_CONTROL(2); 68 shift = 17; 69 mask = 0x0f; 70 value = led->new_brightness >> 4; 71 break; 72 case MC13783_LED_R1: 73 case MC13783_LED_G1: 74 case MC13783_LED_B1: 75 case MC13783_LED_R2: 76 case MC13783_LED_G2: 77 case MC13783_LED_B2: 78 case MC13783_LED_R3: 79 case MC13783_LED_G3: 80 case MC13783_LED_B3: 81 off = led->id - MC13783_LED_R1; 82 bank = off / 3; 83 reg = MC13XXX_REG_LED_CONTROL(3) + bank; 84 shift = (off - bank * 3) * 5 + 6; 85 value = led->new_brightness >> 3; 86 mask = 0x1f; 87 break; 88 case MC13892_LED_MD: 89 reg = MC13XXX_REG_LED_CONTROL(0); 90 shift = 3; 91 mask = 0x3f; 92 value = led->new_brightness >> 2; 93 break; 94 case MC13892_LED_AD: 95 reg = MC13XXX_REG_LED_CONTROL(0); 96 shift = 15; 97 mask = 0x3f; 98 value = led->new_brightness >> 2; 99 break; 100 case MC13892_LED_KP: 101 reg = MC13XXX_REG_LED_CONTROL(1); 102 shift = 3; 103 mask = 0x3f; 104 value = led->new_brightness >> 2; 105 break; 106 case MC13892_LED_R: 107 case MC13892_LED_G: 108 case MC13892_LED_B: 109 off = led->id - MC13892_LED_R; 110 bank = off / 2; 111 reg = MC13XXX_REG_LED_CONTROL(2) + bank; 112 shift = (off - bank * 2) * 12 + 3; 113 value = led->new_brightness >> 2; 114 mask = 0x3f; 115 break; 116 default: 117 BUG(); 118 } 119 120 mc13xxx_reg_rmw(led->master, reg, mask << shift, value << shift); 121 } 122 123 static void mc13xxx_led_set(struct led_classdev *led_cdev, 124 enum led_brightness value) 125 { 126 struct mc13xxx_led *led = 127 container_of(led_cdev, struct mc13xxx_led, cdev); 128 129 led->new_brightness = value; 130 schedule_work(&led->work); 131 } 132 133 static int __init mc13xxx_led_probe(struct platform_device *pdev) 134 { 135 struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); 136 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 137 struct mc13xxx_led_devtype *devtype = 138 (struct mc13xxx_led_devtype *)pdev->id_entry->driver_data; 139 struct mc13xxx_leds *leds; 140 int i, id, num_leds, ret = -ENODATA; 141 u32 reg, init_led = 0; 142 143 if (!pdata) { 144 dev_err(&pdev->dev, "Missing platform data\n"); 145 return -ENODEV; 146 } 147 148 num_leds = pdata->num_leds; 149 150 if ((num_leds < 1) || 151 (num_leds > (devtype->led_max - devtype->led_min + 1))) { 152 dev_err(&pdev->dev, "Invalid LED count %d\n", num_leds); 153 return -EINVAL; 154 } 155 156 leds = devm_kzalloc(&pdev->dev, num_leds * sizeof(struct mc13xxx_led) + 157 sizeof(struct mc13xxx_leds), GFP_KERNEL); 158 if (!leds) 159 return -ENOMEM; 160 161 leds->devtype = devtype; 162 leds->num_leds = num_leds; 163 platform_set_drvdata(pdev, leds); 164 165 for (i = 0; i < devtype->num_regs; i++) { 166 reg = pdata->led_control[i]; 167 WARN_ON(reg >= (1 << 24)); 168 ret = mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), reg); 169 if (ret) 170 return ret; 171 } 172 173 for (i = 0; i < num_leds; i++) { 174 const char *name, *trig; 175 176 ret = -EINVAL; 177 178 id = pdata->led[i].id; 179 name = pdata->led[i].name; 180 trig = pdata->led[i].default_trigger; 181 182 if ((id > devtype->led_max) || (id < devtype->led_min)) { 183 dev_err(&pdev->dev, "Invalid ID %i\n", id); 184 break; 185 } 186 187 if (init_led & (1 << id)) { 188 dev_warn(&pdev->dev, 189 "LED %i already initialized\n", id); 190 break; 191 } 192 193 init_led |= 1 << id; 194 leds->led[i].id = id; 195 leds->led[i].master = mcdev; 196 leds->led[i].cdev.name = name; 197 leds->led[i].cdev.default_trigger = trig; 198 leds->led[i].cdev.brightness_set = mc13xxx_led_set; 199 leds->led[i].cdev.brightness = LED_OFF; 200 201 INIT_WORK(&leds->led[i].work, mc13xxx_led_work); 202 203 ret = led_classdev_register(pdev->dev.parent, 204 &leds->led[i].cdev); 205 if (ret) { 206 dev_err(&pdev->dev, "Failed to register LED %i\n", id); 207 break; 208 } 209 } 210 211 if (ret) 212 while (--i >= 0) { 213 led_classdev_unregister(&leds->led[i].cdev); 214 cancel_work_sync(&leds->led[i].work); 215 } 216 217 return ret; 218 } 219 220 static int mc13xxx_led_remove(struct platform_device *pdev) 221 { 222 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 223 struct mc13xxx_leds *leds = platform_get_drvdata(pdev); 224 int i; 225 226 for (i = 0; i < leds->num_leds; i++) { 227 led_classdev_unregister(&leds->led[i].cdev); 228 cancel_work_sync(&leds->led[i].work); 229 } 230 231 for (i = 0; i < leds->devtype->num_regs; i++) 232 mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), 0); 233 234 return 0; 235 } 236 237 static const struct mc13xxx_led_devtype mc13783_led_devtype = { 238 .led_min = MC13783_LED_MD, 239 .led_max = MC13783_LED_B3, 240 .num_regs = 6, 241 }; 242 243 static const struct mc13xxx_led_devtype mc13892_led_devtype = { 244 .led_min = MC13892_LED_MD, 245 .led_max = MC13892_LED_B, 246 .num_regs = 4, 247 }; 248 249 static const struct platform_device_id mc13xxx_led_id_table[] = { 250 { "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, }, 251 { "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, }, 252 { } 253 }; 254 MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table); 255 256 static struct platform_driver mc13xxx_led_driver = { 257 .driver = { 258 .name = "mc13xxx-led", 259 .owner = THIS_MODULE, 260 }, 261 .remove = mc13xxx_led_remove, 262 .id_table = mc13xxx_led_id_table, 263 }; 264 module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); 265 266 MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC"); 267 MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>"); 268 MODULE_LICENSE("GPL"); 269