leds-mc13783.c revision 9d263813c27e2ad3da7ea0877e623f4ff8767ddd
1/* 2 * LEDs driver for Freescale MC13783 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 28struct mc13xxx_led_devtype { 29 int led_min; 30 int led_max; 31 int num_regs; 32}; 33 34struct 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 42struct mc13xxx_leds { 43 struct mc13xxx_led_devtype *devtype; 44 int num_leds; 45 struct mc13xxx_led led[0]; 46}; 47 48static 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 default: 89 BUG(); 90 } 91 92 mc13xxx_lock(led->master); 93 mc13xxx_reg_rmw(led->master, reg, mask << shift, value << shift); 94 mc13xxx_unlock(led->master); 95} 96 97static void mc13xxx_led_set(struct led_classdev *led_cdev, 98 enum led_brightness value) 99{ 100 struct mc13xxx_led *led = 101 container_of(led_cdev, struct mc13xxx_led, cdev); 102 103 led->new_brightness = value; 104 schedule_work(&led->work); 105} 106 107static int __init mc13xxx_led_setup(struct mc13xxx_led *led, int max_current) 108{ 109 int shift, mask, reg, ret, bank; 110 111 switch (led->id) { 112 case MC13783_LED_MD: 113 reg = MC13XXX_REG_LED_CONTROL(2); 114 shift = 0; 115 mask = 0x07; 116 break; 117 case MC13783_LED_AD: 118 reg = MC13XXX_REG_LED_CONTROL(2); 119 shift = 3; 120 mask = 0x07; 121 break; 122 case MC13783_LED_KP: 123 reg = MC13XXX_REG_LED_CONTROL(2); 124 shift = 6; 125 mask = 0x07; 126 break; 127 case MC13783_LED_R1: 128 case MC13783_LED_G1: 129 case MC13783_LED_B1: 130 case MC13783_LED_R2: 131 case MC13783_LED_G2: 132 case MC13783_LED_B2: 133 case MC13783_LED_R3: 134 case MC13783_LED_G3: 135 case MC13783_LED_B3: 136 bank = (led->id - MC13783_LED_R1) / 3; 137 reg = MC13XXX_REG_LED_CONTROL(3) + bank; 138 shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2; 139 mask = 0x03; 140 break; 141 default: 142 BUG(); 143 } 144 145 mc13xxx_lock(led->master); 146 ret = mc13xxx_reg_rmw(led->master, reg, mask << shift, 147 max_current << shift); 148 mc13xxx_unlock(led->master); 149 150 return ret; 151} 152 153static int __init mc13xxx_led_probe(struct platform_device *pdev) 154{ 155 struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); 156 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 157 struct mc13xxx_led_devtype *devtype = 158 (struct mc13xxx_led_devtype *)pdev->id_entry->driver_data; 159 struct mc13xxx_leds *leds; 160 int i, id, num_leds, ret; 161 u32 reg, init_led = 0; 162 163 if (!pdata) { 164 dev_err(&pdev->dev, "Missing platform data\n"); 165 return -ENODEV; 166 } 167 168 num_leds = pdata->num_leds; 169 170 if ((num_leds < 1) || 171 (num_leds > (devtype->led_max - devtype->led_min + 1))) { 172 dev_err(&pdev->dev, "Invalid LED count %d\n", num_leds); 173 return -EINVAL; 174 } 175 176 leds = devm_kzalloc(&pdev->dev, num_leds * sizeof(struct mc13xxx_led) + 177 sizeof(struct mc13xxx_leds), GFP_KERNEL); 178 if (!leds) 179 return -ENOMEM; 180 181 leds->devtype = devtype; 182 leds->num_leds = num_leds; 183 platform_set_drvdata(pdev, leds); 184 185 mc13xxx_lock(mcdev); 186 for (i = 0; i < devtype->num_regs; i++) { 187 reg = pdata->led_control[i]; 188 WARN_ON(reg >= (1 << 24)); 189 ret = mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), reg); 190 if (ret) 191 break; 192 } 193 mc13xxx_unlock(mcdev); 194 195 if (ret) { 196 dev_err(&pdev->dev, "Unable to init LED driver\n"); 197 return ret; 198 } 199 200 for (i = 0; i < num_leds; i++) { 201 const char *name, *trig; 202 char max_current; 203 204 ret = -EINVAL; 205 206 id = pdata->led[i].id; 207 name = pdata->led[i].name; 208 trig = pdata->led[i].default_trigger; 209 max_current = pdata->led[i].max_current; 210 211 if ((id > devtype->led_max) || (id < devtype->led_min)) { 212 dev_err(&pdev->dev, "Invalid ID %i\n", id); 213 break; 214 } 215 216 if (init_led & (1 << id)) { 217 dev_warn(&pdev->dev, 218 "LED %i already initialized\n", id); 219 break; 220 } 221 222 init_led |= 1 << id; 223 leds->led[i].id = id; 224 leds->led[i].master = mcdev; 225 leds->led[i].cdev.name = name; 226 leds->led[i].cdev.default_trigger = trig; 227 leds->led[i].cdev.brightness_set = mc13xxx_led_set; 228 leds->led[i].cdev.brightness = LED_OFF; 229 230 INIT_WORK(&leds->led[i].work, mc13xxx_led_work); 231 232 ret = mc13xxx_led_setup(&leds->led[i], max_current); 233 if (ret) { 234 dev_err(&pdev->dev, "Unable to setup LED %i\n", id); 235 break; 236 } 237 ret = led_classdev_register(pdev->dev.parent, 238 &leds->led[i].cdev); 239 if (ret) { 240 dev_err(&pdev->dev, "Failed to register LED %i\n", id); 241 break; 242 } 243 } 244 245 if (ret) 246 while (--i >= 0) { 247 led_classdev_unregister(&leds->led[i].cdev); 248 cancel_work_sync(&leds->led[i].work); 249 } 250 251 return ret; 252} 253 254static int mc13xxx_led_remove(struct platform_device *pdev) 255{ 256 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 257 struct mc13xxx_leds *leds = platform_get_drvdata(pdev); 258 int i; 259 260 for (i = 0; i < leds->num_leds; i++) { 261 led_classdev_unregister(&leds->led[i].cdev); 262 cancel_work_sync(&leds->led[i].work); 263 } 264 265 mc13xxx_lock(mcdev); 266 for (i = 0; i < leds->devtype->num_regs; i++) 267 mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), 0); 268 mc13xxx_unlock(mcdev); 269 270 return 0; 271} 272 273static const struct mc13xxx_led_devtype mc13783_led_devtype = { 274 .led_min = MC13783_LED_MD, 275 .led_max = MC13783_LED_B3, 276 .num_regs = 6, 277}; 278 279static const struct platform_device_id mc13xxx_led_id_table[] = { 280 { "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, }, 281 { } 282}; 283MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table); 284 285static struct platform_driver mc13xxx_led_driver = { 286 .driver = { 287 .name = "mc13xxx-led", 288 .owner = THIS_MODULE, 289 }, 290 .remove = mc13xxx_led_remove, 291 .id_table = mc13xxx_led_id_table, 292}; 293module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); 294 295MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC"); 296MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>"); 297MODULE_LICENSE("GPL"); 298