1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (C) 2011 Paul Parsons <lost.distance@yahoo.com>
4*4882a593Smuzhiyun */
5*4882a593Smuzhiyun
6*4882a593Smuzhiyun #include <linux/kernel.h>
7*4882a593Smuzhiyun #include <linux/platform_device.h>
8*4882a593Smuzhiyun #include <linux/leds.h>
9*4882a593Smuzhiyun #include <linux/slab.h>
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <linux/mfd/asic3.h>
12*4882a593Smuzhiyun #include <linux/mfd/core.h>
13*4882a593Smuzhiyun #include <linux/module.h>
14*4882a593Smuzhiyun
15*4882a593Smuzhiyun /*
16*4882a593Smuzhiyun * The HTC ASIC3 LED GPIOs are inputs, not outputs.
17*4882a593Smuzhiyun * Hence we turn the LEDs on/off via the TimeBase register.
18*4882a593Smuzhiyun */
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun /*
21*4882a593Smuzhiyun * When TimeBase is 4 the clock resolution is about 32Hz.
22*4882a593Smuzhiyun * This driver supports hardware blinking with an on+off
23*4882a593Smuzhiyun * period from 62ms (2 clocks) to 125s (4000 clocks).
24*4882a593Smuzhiyun */
25*4882a593Smuzhiyun #define MS_TO_CLK(ms) DIV_ROUND_CLOSEST(((ms)*1024), 32000)
26*4882a593Smuzhiyun #define CLK_TO_MS(clk) (((clk)*32000)/1024)
27*4882a593Smuzhiyun #define MAX_CLK 4000 /* Fits into 12-bit Time registers */
28*4882a593Smuzhiyun #define MAX_MS CLK_TO_MS(MAX_CLK)
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun static const unsigned int led_n_base[ASIC3_NUM_LEDS] = {
31*4882a593Smuzhiyun [0] = ASIC3_LED_0_Base,
32*4882a593Smuzhiyun [1] = ASIC3_LED_1_Base,
33*4882a593Smuzhiyun [2] = ASIC3_LED_2_Base,
34*4882a593Smuzhiyun };
35*4882a593Smuzhiyun
brightness_set(struct led_classdev * cdev,enum led_brightness value)36*4882a593Smuzhiyun static void brightness_set(struct led_classdev *cdev,
37*4882a593Smuzhiyun enum led_brightness value)
38*4882a593Smuzhiyun {
39*4882a593Smuzhiyun struct platform_device *pdev = to_platform_device(cdev->dev->parent);
40*4882a593Smuzhiyun const struct mfd_cell *cell = mfd_get_cell(pdev);
41*4882a593Smuzhiyun struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
42*4882a593Smuzhiyun u32 timebase;
43*4882a593Smuzhiyun unsigned int base;
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun timebase = (value == LED_OFF) ? 0 : (LED_EN|0x4);
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun base = led_n_base[cell->id];
48*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), 32);
49*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_DutyTime), 32);
50*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
51*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_TimeBase), timebase);
52*4882a593Smuzhiyun }
53*4882a593Smuzhiyun
blink_set(struct led_classdev * cdev,unsigned long * delay_on,unsigned long * delay_off)54*4882a593Smuzhiyun static int blink_set(struct led_classdev *cdev,
55*4882a593Smuzhiyun unsigned long *delay_on,
56*4882a593Smuzhiyun unsigned long *delay_off)
57*4882a593Smuzhiyun {
58*4882a593Smuzhiyun struct platform_device *pdev = to_platform_device(cdev->dev->parent);
59*4882a593Smuzhiyun const struct mfd_cell *cell = mfd_get_cell(pdev);
60*4882a593Smuzhiyun struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
61*4882a593Smuzhiyun u32 on;
62*4882a593Smuzhiyun u32 off;
63*4882a593Smuzhiyun unsigned int base;
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun if (*delay_on > MAX_MS || *delay_off > MAX_MS)
66*4882a593Smuzhiyun return -EINVAL;
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun if (*delay_on == 0 && *delay_off == 0) {
69*4882a593Smuzhiyun /* If both are zero then a sensible default should be chosen */
70*4882a593Smuzhiyun on = MS_TO_CLK(500);
71*4882a593Smuzhiyun off = MS_TO_CLK(500);
72*4882a593Smuzhiyun } else {
73*4882a593Smuzhiyun on = MS_TO_CLK(*delay_on);
74*4882a593Smuzhiyun off = MS_TO_CLK(*delay_off);
75*4882a593Smuzhiyun if ((on + off) > MAX_CLK)
76*4882a593Smuzhiyun return -EINVAL;
77*4882a593Smuzhiyun }
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun base = led_n_base[cell->id];
80*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), (on + off));
81*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_DutyTime), on);
82*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
83*4882a593Smuzhiyun asic3_write_register(asic, (base + ASIC3_LED_TimeBase), (LED_EN|0x4));
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun *delay_on = CLK_TO_MS(on);
86*4882a593Smuzhiyun *delay_off = CLK_TO_MS(off);
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun return 0;
89*4882a593Smuzhiyun }
90*4882a593Smuzhiyun
asic3_led_probe(struct platform_device * pdev)91*4882a593Smuzhiyun static int asic3_led_probe(struct platform_device *pdev)
92*4882a593Smuzhiyun {
93*4882a593Smuzhiyun struct asic3_led *led = dev_get_platdata(&pdev->dev);
94*4882a593Smuzhiyun int ret;
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun ret = mfd_cell_enable(pdev);
97*4882a593Smuzhiyun if (ret < 0)
98*4882a593Smuzhiyun return ret;
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev),
101*4882a593Smuzhiyun GFP_KERNEL);
102*4882a593Smuzhiyun if (!led->cdev) {
103*4882a593Smuzhiyun ret = -ENOMEM;
104*4882a593Smuzhiyun goto out;
105*4882a593Smuzhiyun }
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun led->cdev->name = led->name;
108*4882a593Smuzhiyun led->cdev->flags = LED_CORE_SUSPENDRESUME;
109*4882a593Smuzhiyun led->cdev->brightness_set = brightness_set;
110*4882a593Smuzhiyun led->cdev->blink_set = blink_set;
111*4882a593Smuzhiyun led->cdev->default_trigger = led->default_trigger;
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun ret = led_classdev_register(&pdev->dev, led->cdev);
114*4882a593Smuzhiyun if (ret < 0)
115*4882a593Smuzhiyun goto out;
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun return 0;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun out:
120*4882a593Smuzhiyun (void) mfd_cell_disable(pdev);
121*4882a593Smuzhiyun return ret;
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun
asic3_led_remove(struct platform_device * pdev)124*4882a593Smuzhiyun static int asic3_led_remove(struct platform_device *pdev)
125*4882a593Smuzhiyun {
126*4882a593Smuzhiyun struct asic3_led *led = dev_get_platdata(&pdev->dev);
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun led_classdev_unregister(led->cdev);
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun return mfd_cell_disable(pdev);
131*4882a593Smuzhiyun }
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun #ifdef CONFIG_PM_SLEEP
asic3_led_suspend(struct device * dev)134*4882a593Smuzhiyun static int asic3_led_suspend(struct device *dev)
135*4882a593Smuzhiyun {
136*4882a593Smuzhiyun struct platform_device *pdev = to_platform_device(dev);
137*4882a593Smuzhiyun const struct mfd_cell *cell = mfd_get_cell(pdev);
138*4882a593Smuzhiyun int ret;
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun ret = 0;
141*4882a593Smuzhiyun if (cell->suspend)
142*4882a593Smuzhiyun ret = (*cell->suspend)(pdev);
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun return ret;
145*4882a593Smuzhiyun }
146*4882a593Smuzhiyun
asic3_led_resume(struct device * dev)147*4882a593Smuzhiyun static int asic3_led_resume(struct device *dev)
148*4882a593Smuzhiyun {
149*4882a593Smuzhiyun struct platform_device *pdev = to_platform_device(dev);
150*4882a593Smuzhiyun const struct mfd_cell *cell = mfd_get_cell(pdev);
151*4882a593Smuzhiyun int ret;
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun ret = 0;
154*4882a593Smuzhiyun if (cell->resume)
155*4882a593Smuzhiyun ret = (*cell->resume)(pdev);
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun return ret;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun #endif
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun static SIMPLE_DEV_PM_OPS(asic3_led_pm_ops, asic3_led_suspend, asic3_led_resume);
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun static struct platform_driver asic3_led_driver = {
164*4882a593Smuzhiyun .probe = asic3_led_probe,
165*4882a593Smuzhiyun .remove = asic3_led_remove,
166*4882a593Smuzhiyun .driver = {
167*4882a593Smuzhiyun .name = "leds-asic3",
168*4882a593Smuzhiyun .pm = &asic3_led_pm_ops,
169*4882a593Smuzhiyun },
170*4882a593Smuzhiyun };
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun module_platform_driver(asic3_led_driver);
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
175*4882a593Smuzhiyun MODULE_DESCRIPTION("HTC ASIC3 LED driver");
176*4882a593Smuzhiyun MODULE_LICENSE("GPL");
177*4882a593Smuzhiyun MODULE_ALIAS("platform:leds-asic3");
178