linux/drivers/leds/leds-alix2.c
<<
>>
Prefs
   1/*
   2 * LEDs driver for PCEngines ALIX.2 and ALIX.3
   3 *
   4 * Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
   5 */
   6
   7#include <linux/err.h>
   8#include <linux/io.h>
   9#include <linux/kernel.h>
  10#include <linux/leds.h>
  11#include <linux/module.h>
  12#include <linux/platform_device.h>
  13#include <linux/string.h>
  14#include <linux/pci.h>
  15
  16static int force = 0;
  17module_param(force, bool, 0444);
  18MODULE_PARM_DESC(force, "Assume system has ALIX.2/ALIX.3 style LEDs");
  19
  20#define MSR_LBAR_GPIO           0x5140000C
  21#define CS5535_GPIO_SIZE        256
  22
  23static u32 gpio_base;
  24
  25static struct pci_device_id divil_pci[] = {
  26        { PCI_DEVICE(PCI_VENDOR_ID_NS,  PCI_DEVICE_ID_NS_CS5535_ISA) },
  27        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) },
  28        { } /* NULL entry */
  29};
  30MODULE_DEVICE_TABLE(pci, divil_pci);
  31
  32struct alix_led {
  33        struct led_classdev cdev;
  34        unsigned short port;
  35        unsigned int on_value;
  36        unsigned int off_value;
  37};
  38
  39static void alix_led_set(struct led_classdev *led_cdev,
  40                         enum led_brightness brightness)
  41{
  42        struct alix_led *led_dev =
  43                container_of(led_cdev, struct alix_led, cdev);
  44
  45        if (brightness)
  46                outl(led_dev->on_value, gpio_base + led_dev->port);
  47        else
  48                outl(led_dev->off_value, gpio_base + led_dev->port);
  49}
  50
  51static struct alix_led alix_leds[] = {
  52        {
  53                .cdev = {
  54                        .name = "alix:1",
  55                        .brightness_set = alix_led_set,
  56                },
  57                .port = 0x00,
  58                .on_value = 1 << 22,
  59                .off_value = 1 << 6,
  60        },
  61        {
  62                .cdev = {
  63                        .name = "alix:2",
  64                        .brightness_set = alix_led_set,
  65                },
  66                .port = 0x80,
  67                .on_value = 1 << 25,
  68                .off_value = 1 << 9,
  69        },
  70        {
  71                .cdev = {
  72                        .name = "alix:3",
  73                        .brightness_set = alix_led_set,
  74                },
  75                .port = 0x80,
  76                .on_value = 1 << 27,
  77                .off_value = 1 << 11,
  78        },
  79};
  80
  81static int __init alix_led_probe(struct platform_device *pdev)
  82{
  83        int i;
  84        int ret;
  85
  86        for (i = 0; i < ARRAY_SIZE(alix_leds); i++) {
  87                alix_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
  88                ret = led_classdev_register(&pdev->dev, &alix_leds[i].cdev);
  89                if (ret < 0)
  90                        goto fail;
  91        }
  92        return 0;
  93
  94fail:
  95        while (--i >= 0)
  96                led_classdev_unregister(&alix_leds[i].cdev);
  97        return ret;
  98}
  99
 100static int alix_led_remove(struct platform_device *pdev)
 101{
 102        int i;
 103
 104        for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
 105                led_classdev_unregister(&alix_leds[i].cdev);
 106        return 0;
 107}
 108
 109static struct platform_driver alix_led_driver = {
 110        .remove = alix_led_remove,
 111        .driver = {
 112                .name = KBUILD_MODNAME,
 113                .owner = THIS_MODULE,
 114        },
 115};
 116
 117static int __init alix_present(unsigned long bios_phys,
 118                                const char *alix_sig,
 119                                size_t alix_sig_len)
 120{
 121        const size_t bios_len = 0x00010000;
 122        const char *bios_virt;
 123        const char *scan_end;
 124        const char *p;
 125        char name[64];
 126
 127        if (force) {
 128                printk(KERN_NOTICE "%s: forced to skip BIOS test, "
 129                       "assume system has ALIX.2 style LEDs\n",
 130                       KBUILD_MODNAME);
 131                return 1;
 132        }
 133
 134        bios_virt = phys_to_virt(bios_phys);
 135        scan_end = bios_virt + bios_len - (alix_sig_len + 2);
 136        for (p = bios_virt; p < scan_end; p++) {
 137                const char *tail;
 138                char *a;
 139
 140                if (memcmp(p, alix_sig, alix_sig_len) != 0)
 141                        continue;
 142
 143                memcpy(name, p, sizeof(name));
 144
 145                /* remove the first \0 character from string */
 146                a = strchr(name, '\0');
 147                if (a)
 148                        *a = ' ';
 149
 150                /* cut the string at a newline */
 151                a = strchr(name, '\r');
 152                if (a)
 153                        *a = '\0';
 154
 155                tail = p + alix_sig_len;
 156                if ((tail[0] == '2' || tail[0] == '3')) {
 157                        printk(KERN_INFO
 158                               "%s: system is recognized as \"%s\"\n",
 159                               KBUILD_MODNAME, name);
 160                        return 1;
 161                }
 162        }
 163
 164        return 0;
 165}
 166
 167static struct platform_device *pdev;
 168
 169static int __init alix_pci_led_init(void)
 170{
 171        u32 low, hi;
 172
 173        if (pci_dev_present(divil_pci) == 0) {
 174                printk(KERN_WARNING KBUILD_MODNAME": DIVIL not found\n");
 175                return -ENODEV;
 176        }
 177
 178        /* Grab the GPIO I/O range */
 179        rdmsr(MSR_LBAR_GPIO, low, hi);
 180
 181        /* Check the mask and whether GPIO is enabled (sanity check) */
 182        if (hi != 0x0000f001) {
 183                printk(KERN_WARNING KBUILD_MODNAME": GPIO not enabled\n");
 184                return -ENODEV;
 185        }
 186
 187        /* Mask off the IO base address */
 188        gpio_base = low & 0x0000ff00;
 189
 190        if (!request_region(gpio_base, CS5535_GPIO_SIZE, KBUILD_MODNAME)) {
 191                printk(KERN_ERR KBUILD_MODNAME": can't allocate I/O for GPIO\n");
 192                return -ENODEV;
 193        }
 194
 195        /* Set GPIO function to output */
 196        outl(1 << 6, gpio_base + 0x04);
 197        outl(1 << 9, gpio_base + 0x84);
 198        outl(1 << 11, gpio_base + 0x84);
 199
 200        return 0;
 201}
 202
 203static int __init alix_led_init(void)
 204{
 205        int ret = -ENODEV;
 206        const char tinybios_sig[] = "PC Engines ALIX.";
 207        const char coreboot_sig[] = "PC Engines\0ALIX.";
 208
 209        if (alix_present(0xf0000, tinybios_sig, sizeof(tinybios_sig) - 1) ||
 210            alix_present(0x500, coreboot_sig, sizeof(coreboot_sig) - 1))
 211                ret = alix_pci_led_init();
 212
 213        if (ret < 0)
 214                return ret;
 215
 216        pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
 217        if (!IS_ERR(pdev)) {
 218                ret = platform_driver_probe(&alix_led_driver, alix_led_probe);
 219                if (ret)
 220                        platform_device_unregister(pdev);
 221        } else
 222                ret = PTR_ERR(pdev);
 223
 224        return ret;
 225}
 226
 227static void __exit alix_led_exit(void)
 228{
 229        platform_device_unregister(pdev);
 230        platform_driver_unregister(&alix_led_driver);
 231        release_region(gpio_base, CS5535_GPIO_SIZE);
 232}
 233
 234module_init(alix_led_init);
 235module_exit(alix_led_exit);
 236
 237MODULE_AUTHOR("Constantin Baranov <const@mimas.ru>");
 238MODULE_DESCRIPTION("PCEngines ALIX.2 and ALIX.3 LED driver");
 239MODULE_LICENSE("GPL");
 240
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.