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