1 /* 2 * LED driver for WM831x status LEDs 3 * 4 * Copyright(C) 2009 Wolfson Microelectronics PLC. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 */ 11 12 #include <linux/kernel.h> 13 #include <linux/platform_device.h> 14 #include <linux/slab.h> 15 #include <linux/leds.h> 16 #include <linux/err.h> 17 #include <linux/mfd/wm831x/core.h> 18 #include <linux/mfd/wm831x/pdata.h> 19 #include <linux/mfd/wm831x/status.h> 20 #include <linux/module.h> 21 22 23 struct wm831x_status { 24 struct led_classdev cdev; 25 struct wm831x *wm831x; 26 struct work_struct work; 27 struct mutex mutex; 28 29 spinlock_t value_lock; 30 int reg; /* Control register */ 31 int reg_val; /* Control register value */ 32 33 int blink; 34 int blink_time; 35 int blink_cyc; 36 int src; 37 enum led_brightness brightness; 38 }; 39 40 #define to_wm831x_status(led_cdev) \ 41 container_of(led_cdev, struct wm831x_status, cdev) 42 43 static void wm831x_status_work(struct work_struct *work) 44 { 45 struct wm831x_status *led = container_of(work, struct wm831x_status, 46 work); 47 unsigned long flags; 48 49 mutex_lock(&led->mutex); 50 51 led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK | 52 WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK); 53 54 spin_lock_irqsave(&led->value_lock, flags); 55 56 led->reg_val |= led->src << WM831X_LED_SRC_SHIFT; 57 if (led->blink) { 58 led->reg_val |= 2 << WM831X_LED_MODE_SHIFT; 59 led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT; 60 led->reg_val |= led->blink_cyc; 61 } else { 62 if (led->brightness != LED_OFF) 63 led->reg_val |= 1 << WM831X_LED_MODE_SHIFT; 64 } 65 66 spin_unlock_irqrestore(&led->value_lock, flags); 67 68 wm831x_reg_write(led->wm831x, led->reg, led->reg_val); 69 70 mutex_unlock(&led->mutex); 71 } 72 73 static void wm831x_status_set(struct led_classdev *led_cdev, 74 enum led_brightness value) 75 { 76 struct wm831x_status *led = to_wm831x_status(led_cdev); 77 unsigned long flags; 78 79 spin_lock_irqsave(&led->value_lock, flags); 80 led->brightness = value; 81 if (value == LED_OFF) 82 led->blink = 0; 83 schedule_work(&led->work); 84 spin_unlock_irqrestore(&led->value_lock, flags); 85 } 86 87 static int wm831x_status_blink_set(struct led_classdev *led_cdev, 88 unsigned long *delay_on, 89 unsigned long *delay_off) 90 { 91 struct wm831x_status *led = to_wm831x_status(led_cdev); 92 unsigned long flags; 93 int ret = 0; 94 95 /* Pick some defaults if we've not been given times */ 96 if (*delay_on == 0 && *delay_off == 0) { 97 *delay_on = 250; 98 *delay_off = 250; 99 } 100 101 spin_lock_irqsave(&led->value_lock, flags); 102 103 /* We only have a limited selection of settings, see if we can 104 * support the configuration we're being given */ 105 switch (*delay_on) { 106 case 1000: 107 led->blink_time = 0; 108 break; 109 case 250: 110 led->blink_time = 1; 111 break; 112 case 125: 113 led->blink_time = 2; 114 break; 115 case 62: 116 case 63: 117 /* Actually 62.5ms */ 118 led->blink_time = 3; 119 break; 120 default: 121 ret = -EINVAL; 122 break; 123 } 124 125 if (ret == 0) { 126 switch (*delay_off / *delay_on) { 127 case 1: 128 led->blink_cyc = 0; 129 break; 130 case 3: 131 led->blink_cyc = 1; 132 break; 133 case 4: 134 led->blink_cyc = 2; 135 break; 136 case 8: 137 led->blink_cyc = 3; 138 break; 139 default: 140 ret = -EINVAL; 141 break; 142 } 143 } 144 145 if (ret == 0) 146 led->blink = 1; 147 else 148 led->blink = 0; 149 150 /* Always update; if we fail turn off blinking since we expect 151 * a software fallback. */ 152 schedule_work(&led->work); 153 154 spin_unlock_irqrestore(&led->value_lock, flags); 155 156 return ret; 157 } 158 159 static const char * const led_src_texts[] = { 160 "otp", 161 "power", 162 "charger", 163 "soft", 164 }; 165 166 static ssize_t wm831x_status_src_show(struct device *dev, 167 struct device_attribute *attr, char *buf) 168 { 169 struct led_classdev *led_cdev = dev_get_drvdata(dev); 170 struct wm831x_status *led = to_wm831x_status(led_cdev); 171 int i; 172 ssize_t ret = 0; 173 174 mutex_lock(&led->mutex); 175 176 for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) 177 if (i == led->src) 178 ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]); 179 else 180 ret += sprintf(&buf[ret], "%s ", led_src_texts[i]); 181 182 mutex_unlock(&led->mutex); 183 184 ret += sprintf(&buf[ret], "\n"); 185 186 return ret; 187 } 188 189 static ssize_t wm831x_status_src_store(struct device *dev, 190 struct device_attribute *attr, 191 const char *buf, size_t size) 192 { 193 struct led_classdev *led_cdev = dev_get_drvdata(dev); 194 struct wm831x_status *led = to_wm831x_status(led_cdev); 195 char name[20]; 196 int i; 197 size_t len; 198 199 name[sizeof(name) - 1] = '\0'; 200 strncpy(name, buf, sizeof(name) - 1); 201 len = strlen(name); 202 203 if (len && name[len - 1] == '\n') 204 name[len - 1] = '\0'; 205 206 for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) { 207 if (!strcmp(name, led_src_texts[i])) { 208 mutex_lock(&led->mutex); 209 210 led->src = i; 211 schedule_work(&led->work); 212 213 mutex_unlock(&led->mutex); 214 } 215 } 216 217 return size; 218 } 219 220 static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store); 221 222 static struct attribute *wm831x_status_attrs[] = { 223 &dev_attr_src.attr, 224 NULL 225 }; 226 ATTRIBUTE_GROUPS(wm831x_status); 227 228 static int wm831x_status_probe(struct platform_device *pdev) 229 { 230 struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); 231 struct wm831x_pdata *chip_pdata; 232 struct wm831x_status_pdata pdata; 233 struct wm831x_status *drvdata; 234 struct resource *res; 235 int id = pdev->id % ARRAY_SIZE(chip_pdata->status); 236 int ret; 237 238 res = platform_get_resource(pdev, IORESOURCE_REG, 0); 239 if (res == NULL) { 240 dev_err(&pdev->dev, "No register resource\n"); 241 return -EINVAL; 242 } 243 244 drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status), 245 GFP_KERNEL); 246 if (!drvdata) 247 return -ENOMEM; 248 platform_set_drvdata(pdev, drvdata); 249 250 drvdata->wm831x = wm831x; 251 drvdata->reg = res->start; 252 253 if (dev_get_platdata(wm831x->dev)) 254 chip_pdata = dev_get_platdata(wm831x->dev); 255 else 256 chip_pdata = NULL; 257 258 memset(&pdata, 0, sizeof(pdata)); 259 if (chip_pdata && chip_pdata->status[id]) 260 memcpy(&pdata, chip_pdata->status[id], sizeof(pdata)); 261 else 262 pdata.name = dev_name(&pdev->dev); 263 264 mutex_init(&drvdata->mutex); 265 INIT_WORK(&drvdata->work, wm831x_status_work); 266 spin_lock_init(&drvdata->value_lock); 267 268 /* We cache the configuration register and read startup values 269 * from it. */ 270 drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg); 271 272 if (drvdata->reg_val & WM831X_LED_MODE_MASK) 273 drvdata->brightness = LED_FULL; 274 else 275 drvdata->brightness = LED_OFF; 276 277 /* Set a default source if configured, otherwise leave the 278 * current hardware setting. 279 */ 280 if (pdata.default_src == WM831X_STATUS_PRESERVE) { 281 drvdata->src = drvdata->reg_val; 282 drvdata->src &= WM831X_LED_SRC_MASK; 283 drvdata->src >>= WM831X_LED_SRC_SHIFT; 284 } else { 285 drvdata->src = pdata.default_src - 1; 286 } 287 288 drvdata->cdev.name = pdata.name; 289 drvdata->cdev.default_trigger = pdata.default_trigger; 290 drvdata->cdev.brightness_set = wm831x_status_set; 291 drvdata->cdev.blink_set = wm831x_status_blink_set; 292 drvdata->cdev.groups = wm831x_status_groups; 293 294 ret = led_classdev_register(wm831x->dev, &drvdata->cdev); 295 if (ret < 0) { 296 dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); 297 return ret; 298 } 299 300 return 0; 301 } 302 303 static int wm831x_status_remove(struct platform_device *pdev) 304 { 305 struct wm831x_status *drvdata = platform_get_drvdata(pdev); 306 307 led_classdev_unregister(&drvdata->cdev); 308 309 return 0; 310 } 311 312 static struct platform_driver wm831x_status_driver = { 313 .driver = { 314 .name = "wm831x-status", 315 .owner = THIS_MODULE, 316 }, 317 .probe = wm831x_status_probe, 318 .remove = wm831x_status_remove, 319 }; 320 321 module_platform_driver(wm831x_status_driver); 322 323 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 324 MODULE_DESCRIPTION("WM831x status LED driver"); 325 MODULE_LICENSE("GPL"); 326 MODULE_ALIAS("platform:wm831x-status"); 327