Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * drivers/leds/leds-apu.c
0003  * Copyright (C) 2017 Alan Mizrahi, alan at mizrahi dot com dot ve
0004  *
0005  * Redistribution and use in source and binary forms, with or without
0006  * modification, are permitted provided that the following conditions are met:
0007  *
0008  * 1. Redistributions of source code must retain the above copyright
0009  *    notice, this list of conditions and the following disclaimer.
0010  * 2. Redistributions in binary form must reproduce the above copyright
0011  *    notice, this list of conditions and the following disclaimer in the
0012  *    documentation and/or other materials provided with the distribution.
0013  * 3. Neither the names of the copyright holders nor the names of its
0014  *    contributors may be used to endorse or promote products derived from
0015  *    this software without specific prior written permission.
0016  *
0017  * Alternatively, this software may be distributed under the terms of the
0018  * GNU General Public License ("GPL") version 2 as published by the Free
0019  * Software Foundation.
0020  *
0021  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0022  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0023  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0024  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0025  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0026  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0027  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0028  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0029  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0030  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0031  * POSSIBILITY OF SUCH DAMAGE.
0032  */
0033 
0034 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0035 
0036 #include <linux/dmi.h>
0037 #include <linux/err.h>
0038 #include <linux/init.h>
0039 #include <linux/io.h>
0040 #include <linux/kernel.h>
0041 #include <linux/leds.h>
0042 #include <linux/module.h>
0043 #include <linux/platform_device.h>
0044 
0045 #define APU1_FCH_ACPI_MMIO_BASE 0xFED80000
0046 #define APU1_FCH_GPIO_BASE      (APU1_FCH_ACPI_MMIO_BASE + 0x01BD)
0047 #define APU1_LEDON              0x08
0048 #define APU1_LEDOFF             0xC8
0049 #define APU1_NUM_GPIO           3
0050 #define APU1_IOSIZE             sizeof(u8)
0051 
0052 /* LED access parameters */
0053 struct apu_param {
0054     void __iomem *addr; /* for ioread/iowrite */
0055 };
0056 
0057 /* LED private data */
0058 struct apu_led_priv {
0059     struct led_classdev cdev;
0060     struct apu_param param;
0061 };
0062 #define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev)
0063 
0064 /* LED profile */
0065 struct apu_led_profile {
0066     const char *name;
0067     enum led_brightness brightness;
0068     unsigned long offset; /* for devm_ioremap */
0069 };
0070 
0071 struct apu_led_pdata {
0072     struct platform_device *pdev;
0073     struct apu_led_priv *pled;
0074     spinlock_t lock;
0075 };
0076 
0077 static struct apu_led_pdata *apu_led;
0078 
0079 static const struct apu_led_profile apu1_led_profile[] = {
0080     { "apu:green:1", LED_ON,  APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE },
0081     { "apu:green:2", LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE },
0082     { "apu:green:3", LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE },
0083 };
0084 
0085 static const struct dmi_system_id apu_led_dmi_table[] __initconst = {
0086     /* PC Engines APU with factory bios "SageBios_PCEngines_APU-45" */
0087     {
0088         .ident = "apu",
0089         .matches = {
0090             DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
0091             DMI_MATCH(DMI_PRODUCT_NAME, "APU")
0092         }
0093     },
0094     /* PC Engines APU with "Mainline" bios >= 4.6.8 */
0095     {
0096         .ident = "apu",
0097         .matches = {
0098             DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
0099             DMI_MATCH(DMI_PRODUCT_NAME, "apu1")
0100         }
0101     },
0102     {}
0103 };
0104 MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table);
0105 
0106 static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value)
0107 {
0108     struct apu_led_priv *pled = cdev_to_priv(led);
0109 
0110     spin_lock(&apu_led->lock);
0111     iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr);
0112     spin_unlock(&apu_led->lock);
0113 }
0114 
0115 static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld)
0116 {
0117     int i;
0118     int err;
0119 
0120     apu_led->pled = devm_kcalloc(dev,
0121         ARRAY_SIZE(apu1_led_profile), sizeof(struct apu_led_priv),
0122         GFP_KERNEL);
0123 
0124     if (!apu_led->pled)
0125         return -ENOMEM;
0126 
0127     for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++) {
0128         struct apu_led_priv *pled = &apu_led->pled[i];
0129         struct led_classdev *led_cdev = &pled->cdev;
0130 
0131         led_cdev->name = apu1_led_profile[i].name;
0132         led_cdev->brightness = apu1_led_profile[i].brightness;
0133         led_cdev->max_brightness = 1;
0134         led_cdev->flags = LED_CORE_SUSPENDRESUME;
0135         led_cdev->brightness_set = apu1_led_brightness_set;
0136 
0137         pled->param.addr = devm_ioremap(dev,
0138                 apu1_led_profile[i].offset, APU1_IOSIZE);
0139         if (!pled->param.addr) {
0140             err = -ENOMEM;
0141             goto error;
0142         }
0143 
0144         err = led_classdev_register(dev, led_cdev);
0145         if (err)
0146             goto error;
0147 
0148         apu1_led_brightness_set(led_cdev, apu1_led_profile[i].brightness);
0149     }
0150 
0151     return 0;
0152 
0153 error:
0154     while (i-- > 0)
0155         led_classdev_unregister(&apu_led->pled[i].cdev);
0156 
0157     return err;
0158 }
0159 
0160 static int __init apu_led_probe(struct platform_device *pdev)
0161 {
0162     apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL);
0163 
0164     if (!apu_led)
0165         return -ENOMEM;
0166 
0167     apu_led->pdev = pdev;
0168 
0169     spin_lock_init(&apu_led->lock);
0170     return apu_led_config(&pdev->dev, apu_led);
0171 }
0172 
0173 static struct platform_driver apu_led_driver = {
0174     .driver = {
0175         .name = KBUILD_MODNAME,
0176     },
0177 };
0178 
0179 static int __init apu_led_init(void)
0180 {
0181     struct platform_device *pdev;
0182     int err;
0183 
0184     if (!(dmi_match(DMI_SYS_VENDOR, "PC Engines") &&
0185           (dmi_match(DMI_PRODUCT_NAME, "APU") || dmi_match(DMI_PRODUCT_NAME, "apu1")))) {
0186         pr_err("No PC Engines APUv1 board detected. For APUv2,3 support, enable CONFIG_PCENGINES_APU2\n");
0187         return -ENODEV;
0188     }
0189 
0190     pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
0191     if (IS_ERR(pdev)) {
0192         pr_err("Device allocation failed\n");
0193         return PTR_ERR(pdev);
0194     }
0195 
0196     err = platform_driver_probe(&apu_led_driver, apu_led_probe);
0197     if (err) {
0198         pr_err("Probe platform driver failed\n");
0199         platform_device_unregister(pdev);
0200     }
0201 
0202     return err;
0203 }
0204 
0205 static void __exit apu_led_exit(void)
0206 {
0207     int i;
0208 
0209     for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++)
0210         led_classdev_unregister(&apu_led->pled[i].cdev);
0211 
0212     platform_device_unregister(apu_led->pdev);
0213     platform_driver_unregister(&apu_led_driver);
0214 }
0215 
0216 module_init(apu_led_init);
0217 module_exit(apu_led_exit);
0218 
0219 MODULE_AUTHOR("Alan Mizrahi");
0220 MODULE_DESCRIPTION("PC Engines APU1 front LED driver");
0221 MODULE_LICENSE("GPL v2");
0222 MODULE_ALIAS("platform:leds_apu");