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