162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LED driver for WM831x status LEDs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright(C) 2009 Wolfson Microelectronics PLC. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/kernel.h> 962306a36Sopenharmony_ci#include <linux/platform_device.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/leds.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/mfd/wm831x/core.h> 1462306a36Sopenharmony_ci#include <linux/mfd/wm831x/pdata.h> 1562306a36Sopenharmony_ci#include <linux/mfd/wm831x/status.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct wm831x_status { 2062306a36Sopenharmony_ci struct led_classdev cdev; 2162306a36Sopenharmony_ci struct wm831x *wm831x; 2262306a36Sopenharmony_ci struct mutex mutex; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci spinlock_t value_lock; 2562306a36Sopenharmony_ci int reg; /* Control register */ 2662306a36Sopenharmony_ci int reg_val; /* Control register value */ 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci int blink; 2962306a36Sopenharmony_ci int blink_time; 3062306a36Sopenharmony_ci int blink_cyc; 3162306a36Sopenharmony_ci int src; 3262306a36Sopenharmony_ci enum led_brightness brightness; 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define to_wm831x_status(led_cdev) \ 3662306a36Sopenharmony_ci container_of(led_cdev, struct wm831x_status, cdev) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic void wm831x_status_set(struct wm831x_status *led) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci unsigned long flags; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci mutex_lock(&led->mutex); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK | 4562306a36Sopenharmony_ci WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci spin_lock_irqsave(&led->value_lock, flags); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci led->reg_val |= led->src << WM831X_LED_SRC_SHIFT; 5062306a36Sopenharmony_ci if (led->blink) { 5162306a36Sopenharmony_ci led->reg_val |= 2 << WM831X_LED_MODE_SHIFT; 5262306a36Sopenharmony_ci led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT; 5362306a36Sopenharmony_ci led->reg_val |= led->blink_cyc; 5462306a36Sopenharmony_ci } else { 5562306a36Sopenharmony_ci if (led->brightness != LED_OFF) 5662306a36Sopenharmony_ci led->reg_val |= 1 << WM831X_LED_MODE_SHIFT; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci spin_unlock_irqrestore(&led->value_lock, flags); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci wm831x_reg_write(led->wm831x, led->reg, led->reg_val); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci mutex_unlock(&led->mutex); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int wm831x_status_brightness_set(struct led_classdev *led_cdev, 6762306a36Sopenharmony_ci enum led_brightness value) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct wm831x_status *led = to_wm831x_status(led_cdev); 7062306a36Sopenharmony_ci unsigned long flags; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci spin_lock_irqsave(&led->value_lock, flags); 7362306a36Sopenharmony_ci led->brightness = value; 7462306a36Sopenharmony_ci if (value == LED_OFF) 7562306a36Sopenharmony_ci led->blink = 0; 7662306a36Sopenharmony_ci spin_unlock_irqrestore(&led->value_lock, flags); 7762306a36Sopenharmony_ci wm831x_status_set(led); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci return 0; 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int wm831x_status_blink_set(struct led_classdev *led_cdev, 8362306a36Sopenharmony_ci unsigned long *delay_on, 8462306a36Sopenharmony_ci unsigned long *delay_off) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct wm831x_status *led = to_wm831x_status(led_cdev); 8762306a36Sopenharmony_ci unsigned long flags; 8862306a36Sopenharmony_ci int ret = 0; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* Pick some defaults if we've not been given times */ 9162306a36Sopenharmony_ci if (*delay_on == 0 && *delay_off == 0) { 9262306a36Sopenharmony_ci *delay_on = 250; 9362306a36Sopenharmony_ci *delay_off = 250; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci spin_lock_irqsave(&led->value_lock, flags); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* We only have a limited selection of settings, see if we can 9962306a36Sopenharmony_ci * support the configuration we're being given */ 10062306a36Sopenharmony_ci switch (*delay_on) { 10162306a36Sopenharmony_ci case 1000: 10262306a36Sopenharmony_ci led->blink_time = 0; 10362306a36Sopenharmony_ci break; 10462306a36Sopenharmony_ci case 250: 10562306a36Sopenharmony_ci led->blink_time = 1; 10662306a36Sopenharmony_ci break; 10762306a36Sopenharmony_ci case 125: 10862306a36Sopenharmony_ci led->blink_time = 2; 10962306a36Sopenharmony_ci break; 11062306a36Sopenharmony_ci case 62: 11162306a36Sopenharmony_ci case 63: 11262306a36Sopenharmony_ci /* Actually 62.5ms */ 11362306a36Sopenharmony_ci led->blink_time = 3; 11462306a36Sopenharmony_ci break; 11562306a36Sopenharmony_ci default: 11662306a36Sopenharmony_ci ret = -EINVAL; 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (ret == 0) { 12162306a36Sopenharmony_ci switch (*delay_off / *delay_on) { 12262306a36Sopenharmony_ci case 1: 12362306a36Sopenharmony_ci led->blink_cyc = 0; 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci case 3: 12662306a36Sopenharmony_ci led->blink_cyc = 1; 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci case 4: 12962306a36Sopenharmony_ci led->blink_cyc = 2; 13062306a36Sopenharmony_ci break; 13162306a36Sopenharmony_ci case 8: 13262306a36Sopenharmony_ci led->blink_cyc = 3; 13362306a36Sopenharmony_ci break; 13462306a36Sopenharmony_ci default: 13562306a36Sopenharmony_ci ret = -EINVAL; 13662306a36Sopenharmony_ci break; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (ret == 0) 14162306a36Sopenharmony_ci led->blink = 1; 14262306a36Sopenharmony_ci else 14362306a36Sopenharmony_ci led->blink = 0; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci spin_unlock_irqrestore(&led->value_lock, flags); 14662306a36Sopenharmony_ci wm831x_status_set(led); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci return ret; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic const char * const led_src_texts[] = { 15262306a36Sopenharmony_ci "otp", 15362306a36Sopenharmony_ci "power", 15462306a36Sopenharmony_ci "charger", 15562306a36Sopenharmony_ci "soft", 15662306a36Sopenharmony_ci}; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic ssize_t src_show(struct device *dev, 15962306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 16262306a36Sopenharmony_ci struct wm831x_status *led = to_wm831x_status(led_cdev); 16362306a36Sopenharmony_ci int i; 16462306a36Sopenharmony_ci ssize_t ret = 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci mutex_lock(&led->mutex); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) 16962306a36Sopenharmony_ci if (i == led->src) 17062306a36Sopenharmony_ci ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]); 17162306a36Sopenharmony_ci else 17262306a36Sopenharmony_ci ret += sprintf(&buf[ret], "%s ", led_src_texts[i]); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci mutex_unlock(&led->mutex); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci ret += sprintf(&buf[ret], "\n"); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return ret; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic ssize_t src_store(struct device *dev, 18262306a36Sopenharmony_ci struct device_attribute *attr, 18362306a36Sopenharmony_ci const char *buf, size_t size) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci struct led_classdev *led_cdev = dev_get_drvdata(dev); 18662306a36Sopenharmony_ci struct wm831x_status *led = to_wm831x_status(led_cdev); 18762306a36Sopenharmony_ci int i; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci i = sysfs_match_string(led_src_texts, buf); 19062306a36Sopenharmony_ci if (i >= 0) { 19162306a36Sopenharmony_ci mutex_lock(&led->mutex); 19262306a36Sopenharmony_ci led->src = i; 19362306a36Sopenharmony_ci mutex_unlock(&led->mutex); 19462306a36Sopenharmony_ci wm831x_status_set(led); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return size; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(src); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic struct attribute *wm831x_status_attrs[] = { 20362306a36Sopenharmony_ci &dev_attr_src.attr, 20462306a36Sopenharmony_ci NULL 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ciATTRIBUTE_GROUPS(wm831x_status); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic int wm831x_status_probe(struct platform_device *pdev) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); 21162306a36Sopenharmony_ci struct wm831x_pdata *chip_pdata; 21262306a36Sopenharmony_ci struct wm831x_status_pdata pdata; 21362306a36Sopenharmony_ci struct wm831x_status *drvdata; 21462306a36Sopenharmony_ci struct resource *res; 21562306a36Sopenharmony_ci int id = pdev->id % ARRAY_SIZE(chip_pdata->status); 21662306a36Sopenharmony_ci int ret; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_REG, 0); 21962306a36Sopenharmony_ci if (res == NULL) { 22062306a36Sopenharmony_ci dev_err(&pdev->dev, "No register resource\n"); 22162306a36Sopenharmony_ci return -EINVAL; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status), 22562306a36Sopenharmony_ci GFP_KERNEL); 22662306a36Sopenharmony_ci if (!drvdata) 22762306a36Sopenharmony_ci return -ENOMEM; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci drvdata->wm831x = wm831x; 23062306a36Sopenharmony_ci drvdata->reg = res->start; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (dev_get_platdata(wm831x->dev)) 23362306a36Sopenharmony_ci chip_pdata = dev_get_platdata(wm831x->dev); 23462306a36Sopenharmony_ci else 23562306a36Sopenharmony_ci chip_pdata = NULL; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci memset(&pdata, 0, sizeof(pdata)); 23862306a36Sopenharmony_ci if (chip_pdata && chip_pdata->status[id]) 23962306a36Sopenharmony_ci memcpy(&pdata, chip_pdata->status[id], sizeof(pdata)); 24062306a36Sopenharmony_ci else 24162306a36Sopenharmony_ci pdata.name = dev_name(&pdev->dev); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci mutex_init(&drvdata->mutex); 24462306a36Sopenharmony_ci spin_lock_init(&drvdata->value_lock); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* We cache the configuration register and read startup values 24762306a36Sopenharmony_ci * from it. */ 24862306a36Sopenharmony_ci drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci if (drvdata->reg_val & WM831X_LED_MODE_MASK) 25162306a36Sopenharmony_ci drvdata->brightness = LED_FULL; 25262306a36Sopenharmony_ci else 25362306a36Sopenharmony_ci drvdata->brightness = LED_OFF; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* Set a default source if configured, otherwise leave the 25662306a36Sopenharmony_ci * current hardware setting. 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_ci if (pdata.default_src == WM831X_STATUS_PRESERVE) { 25962306a36Sopenharmony_ci drvdata->src = drvdata->reg_val; 26062306a36Sopenharmony_ci drvdata->src &= WM831X_LED_SRC_MASK; 26162306a36Sopenharmony_ci drvdata->src >>= WM831X_LED_SRC_SHIFT; 26262306a36Sopenharmony_ci } else { 26362306a36Sopenharmony_ci drvdata->src = pdata.default_src - 1; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci drvdata->cdev.name = pdata.name; 26762306a36Sopenharmony_ci drvdata->cdev.default_trigger = pdata.default_trigger; 26862306a36Sopenharmony_ci drvdata->cdev.brightness_set_blocking = wm831x_status_brightness_set; 26962306a36Sopenharmony_ci drvdata->cdev.blink_set = wm831x_status_blink_set; 27062306a36Sopenharmony_ci drvdata->cdev.groups = wm831x_status_groups; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci ret = led_classdev_register(wm831x->dev, &drvdata->cdev); 27362306a36Sopenharmony_ci if (ret < 0) { 27462306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); 27562306a36Sopenharmony_ci return ret; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci return 0; 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic int wm831x_status_remove(struct platform_device *pdev) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct wm831x_status *drvdata = platform_get_drvdata(pdev); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci led_classdev_unregister(&drvdata->cdev); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic struct platform_driver wm831x_status_driver = { 29362306a36Sopenharmony_ci .driver = { 29462306a36Sopenharmony_ci .name = "wm831x-status", 29562306a36Sopenharmony_ci }, 29662306a36Sopenharmony_ci .probe = wm831x_status_probe, 29762306a36Sopenharmony_ci .remove = wm831x_status_remove, 29862306a36Sopenharmony_ci}; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cimodule_platform_driver(wm831x_status_driver); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 30362306a36Sopenharmony_ciMODULE_DESCRIPTION("WM831x status LED driver"); 30462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 30562306a36Sopenharmony_ciMODULE_ALIAS("platform:wm831x-status"); 306