Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  *  Copyright (C) 2011 Paul Parsons <lost.distance@yahoo.com>
0004  */
0005 
0006 #include <linux/kernel.h>
0007 #include <linux/platform_device.h>
0008 #include <linux/leds.h>
0009 #include <linux/slab.h>
0010 
0011 #include <linux/mfd/asic3.h>
0012 #include <linux/mfd/core.h>
0013 #include <linux/module.h>
0014 
0015 /*
0016  *  The HTC ASIC3 LED GPIOs are inputs, not outputs.
0017  *  Hence we turn the LEDs on/off via the TimeBase register.
0018  */
0019 
0020 /*
0021  *  When TimeBase is 4 the clock resolution is about 32Hz.
0022  *  This driver supports hardware blinking with an on+off
0023  *  period from 62ms (2 clocks) to 125s (4000 clocks).
0024  */
0025 #define MS_TO_CLK(ms)   DIV_ROUND_CLOSEST(((ms)*1024), 32000)
0026 #define CLK_TO_MS(clk)  (((clk)*32000)/1024)
0027 #define MAX_CLK     4000            /* Fits into 12-bit Time registers */
0028 #define MAX_MS      CLK_TO_MS(MAX_CLK)
0029 
0030 static const unsigned int led_n_base[ASIC3_NUM_LEDS] = {
0031     [0] = ASIC3_LED_0_Base,
0032     [1] = ASIC3_LED_1_Base,
0033     [2] = ASIC3_LED_2_Base,
0034 };
0035 
0036 static void brightness_set(struct led_classdev *cdev,
0037     enum led_brightness value)
0038 {
0039     struct platform_device *pdev = to_platform_device(cdev->dev->parent);
0040     const struct mfd_cell *cell = mfd_get_cell(pdev);
0041     struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
0042     u32 timebase;
0043     unsigned int base;
0044 
0045     timebase = (value == LED_OFF) ? 0 : (LED_EN|0x4);
0046 
0047     base = led_n_base[cell->id];
0048     asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), 32);
0049     asic3_write_register(asic, (base + ASIC3_LED_DutyTime), 32);
0050     asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
0051     asic3_write_register(asic, (base + ASIC3_LED_TimeBase), timebase);
0052 }
0053 
0054 static int blink_set(struct led_classdev *cdev,
0055     unsigned long *delay_on,
0056     unsigned long *delay_off)
0057 {
0058     struct platform_device *pdev = to_platform_device(cdev->dev->parent);
0059     const struct mfd_cell *cell = mfd_get_cell(pdev);
0060     struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
0061     u32 on;
0062     u32 off;
0063     unsigned int base;
0064 
0065     if (*delay_on > MAX_MS || *delay_off > MAX_MS)
0066         return -EINVAL;
0067 
0068     if (*delay_on == 0 && *delay_off == 0) {
0069         /* If both are zero then a sensible default should be chosen */
0070         on = MS_TO_CLK(500);
0071         off = MS_TO_CLK(500);
0072     } else {
0073         on = MS_TO_CLK(*delay_on);
0074         off = MS_TO_CLK(*delay_off);
0075         if ((on + off) > MAX_CLK)
0076             return -EINVAL;
0077     }
0078 
0079     base = led_n_base[cell->id];
0080     asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), (on + off));
0081     asic3_write_register(asic, (base + ASIC3_LED_DutyTime), on);
0082     asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
0083     asic3_write_register(asic, (base + ASIC3_LED_TimeBase), (LED_EN|0x4));
0084 
0085     *delay_on = CLK_TO_MS(on);
0086     *delay_off = CLK_TO_MS(off);
0087 
0088     return 0;
0089 }
0090 
0091 static int asic3_led_probe(struct platform_device *pdev)
0092 {
0093     struct asic3_led *led = dev_get_platdata(&pdev->dev);
0094     int ret;
0095 
0096     ret = mfd_cell_enable(pdev);
0097     if (ret < 0)
0098         return ret;
0099 
0100     led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev),
0101                 GFP_KERNEL);
0102     if (!led->cdev) {
0103         ret = -ENOMEM;
0104         goto out;
0105     }
0106 
0107     led->cdev->name = led->name;
0108     led->cdev->flags = LED_CORE_SUSPENDRESUME;
0109     led->cdev->brightness_set = brightness_set;
0110     led->cdev->blink_set = blink_set;
0111     led->cdev->default_trigger = led->default_trigger;
0112 
0113     ret = led_classdev_register(&pdev->dev, led->cdev);
0114     if (ret < 0)
0115         goto out;
0116 
0117     return 0;
0118 
0119 out:
0120     (void) mfd_cell_disable(pdev);
0121     return ret;
0122 }
0123 
0124 static int asic3_led_remove(struct platform_device *pdev)
0125 {
0126     struct asic3_led *led = dev_get_platdata(&pdev->dev);
0127 
0128     led_classdev_unregister(led->cdev);
0129 
0130     return mfd_cell_disable(pdev);
0131 }
0132 
0133 #ifdef CONFIG_PM_SLEEP
0134 static int asic3_led_suspend(struct device *dev)
0135 {
0136     struct platform_device *pdev = to_platform_device(dev);
0137     const struct mfd_cell *cell = mfd_get_cell(pdev);
0138     int ret;
0139 
0140     ret = 0;
0141     if (cell->suspend)
0142         ret = (*cell->suspend)(pdev);
0143 
0144     return ret;
0145 }
0146 
0147 static int asic3_led_resume(struct device *dev)
0148 {
0149     struct platform_device *pdev = to_platform_device(dev);
0150     const struct mfd_cell *cell = mfd_get_cell(pdev);
0151     int ret;
0152 
0153     ret = 0;
0154     if (cell->resume)
0155         ret = (*cell->resume)(pdev);
0156 
0157     return ret;
0158 }
0159 #endif
0160 
0161 static SIMPLE_DEV_PM_OPS(asic3_led_pm_ops, asic3_led_suspend, asic3_led_resume);
0162 
0163 static struct platform_driver asic3_led_driver = {
0164     .probe      = asic3_led_probe,
0165     .remove     = asic3_led_remove,
0166     .driver     = {
0167         .name   = "leds-asic3",
0168         .pm = &asic3_led_pm_ops,
0169     },
0170 };
0171 
0172 module_platform_driver(asic3_led_driver);
0173 
0174 MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
0175 MODULE_DESCRIPTION("HTC ASIC3 LED driver");
0176 MODULE_LICENSE("GPL");
0177 MODULE_ALIAS("platform:leds-asic3");