Path: blob/master/drivers/leds/leds-wm831x-status.c
15109 views
/*1* LED driver for WM831x status LEDs2*3* Copyright(C) 2009 Wolfson Microelectronics PLC.4*5* This program is free software; you can redistribute it and/or modify6* it under the terms of the GNU General Public License version 2 as7* published by the Free Software Foundation.8*9*/1011#include <linux/kernel.h>12#include <linux/init.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>202122struct wm831x_status {23struct led_classdev cdev;24struct wm831x *wm831x;25struct work_struct work;26struct mutex mutex;2728spinlock_t value_lock;29int reg; /* Control register */30int reg_val; /* Control register value */3132int blink;33int blink_time;34int blink_cyc;35int src;36enum led_brightness brightness;37};3839#define to_wm831x_status(led_cdev) \40container_of(led_cdev, struct wm831x_status, cdev)4142static void wm831x_status_work(struct work_struct *work)43{44struct wm831x_status *led = container_of(work, struct wm831x_status,45work);46unsigned long flags;4748mutex_lock(&led->mutex);4950led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK |51WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK);5253spin_lock_irqsave(&led->value_lock, flags);5455led->reg_val |= led->src << WM831X_LED_SRC_SHIFT;56if (led->blink) {57led->reg_val |= 2 << WM831X_LED_MODE_SHIFT;58led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT;59led->reg_val |= led->blink_cyc;60} else {61if (led->brightness != LED_OFF)62led->reg_val |= 1 << WM831X_LED_MODE_SHIFT;63}6465spin_unlock_irqrestore(&led->value_lock, flags);6667wm831x_reg_write(led->wm831x, led->reg, led->reg_val);6869mutex_unlock(&led->mutex);70}7172static void wm831x_status_set(struct led_classdev *led_cdev,73enum led_brightness value)74{75struct wm831x_status *led = to_wm831x_status(led_cdev);76unsigned long flags;7778spin_lock_irqsave(&led->value_lock, flags);79led->brightness = value;80if (value == LED_OFF)81led->blink = 0;82schedule_work(&led->work);83spin_unlock_irqrestore(&led->value_lock, flags);84}8586static int wm831x_status_blink_set(struct led_classdev *led_cdev,87unsigned long *delay_on,88unsigned long *delay_off)89{90struct wm831x_status *led = to_wm831x_status(led_cdev);91unsigned long flags;92int ret = 0;9394/* Pick some defaults if we've not been given times */95if (*delay_on == 0 && *delay_off == 0) {96*delay_on = 250;97*delay_off = 250;98}99100spin_lock_irqsave(&led->value_lock, flags);101102/* We only have a limited selection of settings, see if we can103* support the configuration we're being given */104switch (*delay_on) {105case 1000:106led->blink_time = 0;107break;108case 250:109led->blink_time = 1;110break;111case 125:112led->blink_time = 2;113break;114case 62:115case 63:116/* Actually 62.5ms */117led->blink_time = 3;118break;119default:120ret = -EINVAL;121break;122}123124if (ret == 0) {125switch (*delay_off / *delay_on) {126case 1:127led->blink_cyc = 0;128break;129case 3:130led->blink_cyc = 1;131break;132case 4:133led->blink_cyc = 2;134break;135case 8:136led->blink_cyc = 3;137break;138default:139ret = -EINVAL;140break;141}142}143144if (ret == 0)145led->blink = 1;146else147led->blink = 0;148149/* Always update; if we fail turn off blinking since we expect150* a software fallback. */151schedule_work(&led->work);152153spin_unlock_irqrestore(&led->value_lock, flags);154155return ret;156}157158static const char *led_src_texts[] = {159"otp",160"power",161"charger",162"soft",163};164165static ssize_t wm831x_status_src_show(struct device *dev,166struct device_attribute *attr, char *buf)167{168struct led_classdev *led_cdev = dev_get_drvdata(dev);169struct wm831x_status *led = to_wm831x_status(led_cdev);170int i;171ssize_t ret = 0;172173mutex_lock(&led->mutex);174175for (i = 0; i < ARRAY_SIZE(led_src_texts); i++)176if (i == led->src)177ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]);178else179ret += sprintf(&buf[ret], "%s ", led_src_texts[i]);180181mutex_unlock(&led->mutex);182183ret += sprintf(&buf[ret], "\n");184185return ret;186}187188static ssize_t wm831x_status_src_store(struct device *dev,189struct device_attribute *attr,190const char *buf, size_t size)191{192struct led_classdev *led_cdev = dev_get_drvdata(dev);193struct wm831x_status *led = to_wm831x_status(led_cdev);194char name[20];195int i;196size_t len;197198name[sizeof(name) - 1] = '\0';199strncpy(name, buf, sizeof(name) - 1);200len = strlen(name);201202if (len && name[len - 1] == '\n')203name[len - 1] = '\0';204205for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) {206if (!strcmp(name, led_src_texts[i])) {207mutex_lock(&led->mutex);208209led->src = i;210schedule_work(&led->work);211212mutex_unlock(&led->mutex);213}214}215216return size;217}218219static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);220221static int wm831x_status_probe(struct platform_device *pdev)222{223struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);224struct wm831x_pdata *chip_pdata;225struct wm831x_status_pdata pdata;226struct wm831x_status *drvdata;227struct resource *res;228int id = pdev->id % ARRAY_SIZE(chip_pdata->status);229int ret;230231res = platform_get_resource(pdev, IORESOURCE_IO, 0);232if (res == NULL) {233dev_err(&pdev->dev, "No I/O resource\n");234ret = -EINVAL;235goto err;236}237238drvdata = kzalloc(sizeof(struct wm831x_status), GFP_KERNEL);239if (!drvdata)240return -ENOMEM;241dev_set_drvdata(&pdev->dev, drvdata);242243drvdata->wm831x = wm831x;244drvdata->reg = res->start;245246if (wm831x->dev->platform_data)247chip_pdata = wm831x->dev->platform_data;248else249chip_pdata = NULL;250251memset(&pdata, 0, sizeof(pdata));252if (chip_pdata && chip_pdata->status[id])253memcpy(&pdata, chip_pdata->status[id], sizeof(pdata));254else255pdata.name = dev_name(&pdev->dev);256257mutex_init(&drvdata->mutex);258INIT_WORK(&drvdata->work, wm831x_status_work);259spin_lock_init(&drvdata->value_lock);260261/* We cache the configuration register and read startup values262* from it. */263drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg);264265if (drvdata->reg_val & WM831X_LED_MODE_MASK)266drvdata->brightness = LED_FULL;267else268drvdata->brightness = LED_OFF;269270/* Set a default source if configured, otherwise leave the271* current hardware setting.272*/273if (pdata.default_src == WM831X_STATUS_PRESERVE) {274drvdata->src = drvdata->reg_val;275drvdata->src &= WM831X_LED_SRC_MASK;276drvdata->src >>= WM831X_LED_SRC_SHIFT;277} else {278drvdata->src = pdata.default_src - 1;279}280281drvdata->cdev.name = pdata.name;282drvdata->cdev.default_trigger = pdata.default_trigger;283drvdata->cdev.brightness_set = wm831x_status_set;284drvdata->cdev.blink_set = wm831x_status_blink_set;285286ret = led_classdev_register(wm831x->dev, &drvdata->cdev);287if (ret < 0) {288dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);289goto err_led;290}291292ret = device_create_file(drvdata->cdev.dev, &dev_attr_src);293if (ret != 0)294dev_err(&pdev->dev,295"No source control for LED: %d\n", ret);296297return 0;298299err_led:300led_classdev_unregister(&drvdata->cdev);301kfree(drvdata);302err:303return ret;304}305306static int wm831x_status_remove(struct platform_device *pdev)307{308struct wm831x_status *drvdata = platform_get_drvdata(pdev);309310device_remove_file(drvdata->cdev.dev, &dev_attr_src);311led_classdev_unregister(&drvdata->cdev);312kfree(drvdata);313314return 0;315}316317static struct platform_driver wm831x_status_driver = {318.driver = {319.name = "wm831x-status",320.owner = THIS_MODULE,321},322.probe = wm831x_status_probe,323.remove = wm831x_status_remove,324};325326static int __devinit wm831x_status_init(void)327{328return platform_driver_register(&wm831x_status_driver);329}330module_init(wm831x_status_init);331332static void wm831x_status_exit(void)333{334platform_driver_unregister(&wm831x_status_driver);335}336module_exit(wm831x_status_exit);337338MODULE_AUTHOR("Mark Brown <[email protected]>");339MODULE_DESCRIPTION("WM831x status LED driver");340MODULE_LICENSE("GPL");341MODULE_ALIAS("platform:wm831x-status");342343344