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                return ret;
 103
 104        led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev),
 105                                GFP_KERNEL);
 106        if (!led->cdev) {
 107                ret = -ENOMEM;
 108                goto out;
 109        }
 110
 111        led->cdev->name = led->name;
 112        led->cdev->flags = LED_CORE_SUSPENDRESUME;
 113        led->cdev->brightness_set = brightness_set;
 114        led->cdev->blink_set = blink_set;
 115        led->cdev->default_trigger = led->default_trigger;
 116
 117        ret = led_classdev_register(&pdev->dev, led->cdev);
 118        if (ret < 0)
 119                goto out;
 120
 121        return 0;
 122
 123out:
 124        (void) mfd_cell_disable(pdev);
 125        return ret;
 126}
 127
 128static int __devexit asic3_led_remove(struct platform_device *pdev)
 129{
 130        struct asic3_led *led = pdev->dev.platform_data;
 131
 132        led_classdev_unregister(led->cdev);
 133
 134        return mfd_cell_disable(pdev);
 135}
 136
 137static int asic3_led_suspend(struct device *dev)
 138{
 139        struct platform_device *pdev = to_platform_device(dev);
 140        const struct mfd_cell *cell = mfd_get_cell(pdev);
 141        int ret;
 142
 143        ret = 0;
 144        if (cell->suspend)
 145                ret = (*cell->suspend)(pdev);
 146
 147        return ret;
 148}
 149
 150static int asic3_led_resume(struct device *dev)
 151{
 152        struct platform_device *pdev = to_platform_device(dev);
 153        const struct mfd_cell *cell = mfd_get_cell(pdev);
 154        int ret;
 155
 156        ret = 0;
 157        if (cell->resume)
 158                ret = (*cell->resume)(pdev);
 159
 160        return ret;
 161}
 162
 163static const struct dev_pm_ops asic3_led_pm_ops = {
 164        .suspend        = asic3_led_suspend,
 165        .resume         = asic3_led_resume,
 166};
 167
 168static struct platform_driver asic3_led_driver = {
 169        .probe          = asic3_led_probe,
 170        .remove         = __devexit_p(asic3_led_remove),
 171        .driver         = {
 172                .name   = "leds-asic3",
 173                .owner  = THIS_MODULE,
 174                .pm     = &asic3_led_pm_ops,
 175        },
 176};
 177
 178module_platform_driver(asic3_led_driver);
 179
 180MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
 181MODULE_DESCRIPTION("HTC ASIC3 LED driver");
 182MODULE_LICENSE("GPL");
 183MODULE_ALIAS("platform:leds-asic3");
 184