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