222 lines · c
1/*2 * drivers/leds/leds-apu.c3 * Copyright (C) 2017 Alan Mizrahi, alan at mizrahi dot com dot ve4 *5 * Redistribution and use in source and binary forms, with or without6 * modification, are permitted provided that the following conditions are met:7 *8 * 1. Redistributions of source code must retain the above copyright9 * notice, this list of conditions and the following disclaimer.10 * 2. Redistributions in binary form must reproduce the above copyright11 * notice, this list of conditions and the following disclaimer in the12 * documentation and/or other materials provided with the distribution.13 * 3. Neither the names of the copyright holders nor the names of its14 * contributors may be used to endorse or promote products derived from15 * this software without specific prior written permission.16 *17 * Alternatively, this software may be distributed under the terms of the18 * GNU General Public License ("GPL") version 2 as published by the Free19 * Software Foundation.20 *21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE31 * POSSIBILITY OF SUCH DAMAGE.32 */33 34#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt35 36#include <linux/dmi.h>37#include <linux/err.h>38#include <linux/init.h>39#include <linux/io.h>40#include <linux/kernel.h>41#include <linux/leds.h>42#include <linux/module.h>43#include <linux/platform_device.h>44 45#define APU1_FCH_ACPI_MMIO_BASE 0xFED8000046#define APU1_FCH_GPIO_BASE (APU1_FCH_ACPI_MMIO_BASE + 0x01BD)47#define APU1_LEDON 0x0848#define APU1_LEDOFF 0xC849#define APU1_NUM_GPIO 350#define APU1_IOSIZE sizeof(u8)51 52/* LED access parameters */53struct apu_param {54 void __iomem *addr; /* for ioread/iowrite */55};56 57/* LED private data */58struct apu_led_priv {59 struct led_classdev cdev;60 struct apu_param param;61};62#define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev)63 64/* LED profile */65struct apu_led_profile {66 const char *name;67 enum led_brightness brightness;68 unsigned long offset; /* for devm_ioremap */69};70 71struct apu_led_pdata {72 struct platform_device *pdev;73 struct apu_led_priv *pled;74 spinlock_t lock;75};76 77static struct apu_led_pdata *apu_led;78 79static const struct apu_led_profile apu1_led_profile[] = {80 { "apu:green:1", LED_ON, APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE },81 { "apu:green:2", LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE },82 { "apu:green:3", LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE },83};84 85static const struct dmi_system_id apu_led_dmi_table[] __initconst = {86 /* PC Engines APU with factory bios "SageBios_PCEngines_APU-45" */87 {88 .ident = "apu",89 .matches = {90 DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),91 DMI_MATCH(DMI_PRODUCT_NAME, "APU")92 }93 },94 /* PC Engines APU with "Mainline" bios >= 4.6.8 */95 {96 .ident = "apu",97 .matches = {98 DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),99 DMI_MATCH(DMI_PRODUCT_NAME, "apu1")100 }101 },102 {}103};104MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table);105 106static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value)107{108 struct apu_led_priv *pled = cdev_to_priv(led);109 110 spin_lock(&apu_led->lock);111 iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr);112 spin_unlock(&apu_led->lock);113}114 115static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld)116{117 int i;118 int err;119 120 apu_led->pled = devm_kcalloc(dev,121 ARRAY_SIZE(apu1_led_profile), sizeof(struct apu_led_priv),122 GFP_KERNEL);123 124 if (!apu_led->pled)125 return -ENOMEM;126 127 for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++) {128 struct apu_led_priv *pled = &apu_led->pled[i];129 struct led_classdev *led_cdev = &pled->cdev;130 131 led_cdev->name = apu1_led_profile[i].name;132 led_cdev->brightness = apu1_led_profile[i].brightness;133 led_cdev->max_brightness = 1;134 led_cdev->flags = LED_CORE_SUSPENDRESUME;135 led_cdev->brightness_set = apu1_led_brightness_set;136 137 pled->param.addr = devm_ioremap(dev,138 apu1_led_profile[i].offset, APU1_IOSIZE);139 if (!pled->param.addr) {140 err = -ENOMEM;141 goto error;142 }143 144 err = led_classdev_register(dev, led_cdev);145 if (err)146 goto error;147 148 apu1_led_brightness_set(led_cdev, apu1_led_profile[i].brightness);149 }150 151 return 0;152 153error:154 while (i-- > 0)155 led_classdev_unregister(&apu_led->pled[i].cdev);156 157 return err;158}159 160static int __init apu_led_probe(struct platform_device *pdev)161{162 apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL);163 164 if (!apu_led)165 return -ENOMEM;166 167 apu_led->pdev = pdev;168 169 spin_lock_init(&apu_led->lock);170 return apu_led_config(&pdev->dev, apu_led);171}172 173static struct platform_driver apu_led_driver = {174 .driver = {175 .name = KBUILD_MODNAME,176 },177};178 179static int __init apu_led_init(void)180{181 struct platform_device *pdev;182 int err;183 184 if (!dmi_check_system(apu_led_dmi_table)) {185 pr_err("No PC Engines APUv1 board detected. For APUv2,3 support, enable CONFIG_PCENGINES_APU2\n");186 return -ENODEV;187 }188 189 pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);190 if (IS_ERR(pdev)) {191 pr_err("Device allocation failed\n");192 return PTR_ERR(pdev);193 }194 195 err = platform_driver_probe(&apu_led_driver, apu_led_probe);196 if (err) {197 pr_err("Probe platform driver failed\n");198 platform_device_unregister(pdev);199 }200 201 return err;202}203 204static void __exit apu_led_exit(void)205{206 int i;207 208 for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++)209 led_classdev_unregister(&apu_led->pled[i].cdev);210 211 platform_device_unregister(apu_led->pdev);212 platform_driver_unregister(&apu_led_driver);213}214 215module_init(apu_led_init);216module_exit(apu_led_exit);217 218MODULE_AUTHOR("Alan Mizrahi");219MODULE_DESCRIPTION("PC Engines APU1 front LED driver");220MODULE_LICENSE("GPL v2");221MODULE_ALIAS("platform:leds_apu");222