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