leds-mc13783.c revision 01a7a063e8cf4bc09af2c02d28c12f3d72bf9649
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 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 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_lock(led->master); 121 mc13xxx_reg_rmw(led->master, reg, mask << shift, value << shift); 122 mc13xxx_unlock(led->master); 123} 124 125static void mc13xxx_led_set(struct led_classdev *led_cdev, 126 enum led_brightness value) 127{ 128 struct mc13xxx_led *led = 129 container_of(led_cdev, struct mc13xxx_led, cdev); 130 131 led->new_brightness = value; 132 schedule_work(&led->work); 133} 134 135static int __init mc13xxx_led_probe(struct platform_device *pdev) 136{ 137 struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); 138 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 139 struct mc13xxx_led_devtype *devtype = 140 (struct mc13xxx_led_devtype *)pdev->id_entry->driver_data; 141 struct mc13xxx_leds *leds; 142 int i, id, num_leds, ret = -ENODATA; 143 u32 reg, init_led = 0; 144 145 if (!pdata) { 146 dev_err(&pdev->dev, "Missing platform data\n"); 147 return -ENODEV; 148 } 149 150 num_leds = pdata->num_leds; 151 152 if ((num_leds < 1) || 153 (num_leds > (devtype->led_max - devtype->led_min + 1))) { 154 dev_err(&pdev->dev, "Invalid LED count %d\n", num_leds); 155 return -EINVAL; 156 } 157 158 leds = devm_kzalloc(&pdev->dev, num_leds * sizeof(struct mc13xxx_led) + 159 sizeof(struct mc13xxx_leds), GFP_KERNEL); 160 if (!leds) 161 return -ENOMEM; 162 163 leds->devtype = devtype; 164 leds->num_leds = num_leds; 165 platform_set_drvdata(pdev, leds); 166 167 mc13xxx_lock(mcdev); 168 for (i = 0; i < devtype->num_regs; i++) { 169 reg = pdata->led_control[i]; 170 WARN_ON(reg >= (1 << 24)); 171 ret = mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), reg); 172 if (ret) 173 break; 174 } 175 mc13xxx_unlock(mcdev); 176 177 if (ret) { 178 dev_err(&pdev->dev, "Unable to init LED driver\n"); 179 return ret; 180 } 181 182 for (i = 0; i < num_leds; i++) { 183 const char *name, *trig; 184 185 ret = -EINVAL; 186 187 id = pdata->led[i].id; 188 name = pdata->led[i].name; 189 trig = pdata->led[i].default_trigger; 190 191 if ((id > devtype->led_max) || (id < devtype->led_min)) { 192 dev_err(&pdev->dev, "Invalid ID %i\n", id); 193 break; 194 } 195 196 if (init_led & (1 << id)) { 197 dev_warn(&pdev->dev, 198 "LED %i already initialized\n", id); 199 break; 200 } 201 202 init_led |= 1 << id; 203 leds->led[i].id = id; 204 leds->led[i].master = mcdev; 205 leds->led[i].cdev.name = name; 206 leds->led[i].cdev.default_trigger = trig; 207 leds->led[i].cdev.brightness_set = mc13xxx_led_set; 208 leds->led[i].cdev.brightness = LED_OFF; 209 210 INIT_WORK(&leds->led[i].work, mc13xxx_led_work); 211 212 ret = led_classdev_register(pdev->dev.parent, 213 &leds->led[i].cdev); 214 if (ret) { 215 dev_err(&pdev->dev, "Failed to register LED %i\n", id); 216 break; 217 } 218 } 219 220 if (ret) 221 while (--i >= 0) { 222 led_classdev_unregister(&leds->led[i].cdev); 223 cancel_work_sync(&leds->led[i].work); 224 } 225 226 return ret; 227} 228 229static int mc13xxx_led_remove(struct platform_device *pdev) 230{ 231 struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent); 232 struct mc13xxx_leds *leds = platform_get_drvdata(pdev); 233 int i; 234 235 for (i = 0; i < leds->num_leds; i++) { 236 led_classdev_unregister(&leds->led[i].cdev); 237 cancel_work_sync(&leds->led[i].work); 238 } 239 240 mc13xxx_lock(mcdev); 241 for (i = 0; i < leds->devtype->num_regs; i++) 242 mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), 0); 243 mc13xxx_unlock(mcdev); 244 245 return 0; 246} 247 248static const struct mc13xxx_led_devtype mc13783_led_devtype = { 249 .led_min = MC13783_LED_MD, 250 .led_max = MC13783_LED_B3, 251 .num_regs = 6, 252}; 253 254static const struct mc13xxx_led_devtype mc13892_led_devtype = { 255 .led_min = MC13892_LED_MD, 256 .led_max = MC13892_LED_B, 257 .num_regs = 4, 258}; 259 260static const struct platform_device_id mc13xxx_led_id_table[] = { 261 { "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, }, 262 { "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, }, 263 { } 264}; 265MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table); 266 267static struct platform_driver mc13xxx_led_driver = { 268 .driver = { 269 .name = "mc13xxx-led", 270 .owner = THIS_MODULE, 271 }, 272 .remove = mc13xxx_led_remove, 273 .id_table = mc13xxx_led_id_table, 274}; 275module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); 276 277MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC"); 278MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>"); 279MODULE_LICENSE("GPL"); 280