brintos

brintos / linux-shallow public Read only

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