162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SS4200-E Hardware API 462306a36Sopenharmony_ci * Copyright (c) 2009, Intel Corporation. 562306a36Sopenharmony_ci * Copyright IBM Corporation, 2009 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Dave Hansen <dave@sr71.net> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/dmi.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/ioport.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/leds.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/pci.h> 1962306a36Sopenharmony_ci#include <linux/types.h> 2062306a36Sopenharmony_ci#include <linux/uaccess.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ciMODULE_AUTHOR("Rodney Girod <rgirod@confocus.com>, Dave Hansen <dave@sr71.net>"); 2362306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel NAS/Home Server ICH7 GPIO Driver"); 2462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* 2762306a36Sopenharmony_ci * ICH7 LPC/GPIO PCI Config register offsets 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci#define PMBASE 0x040 3062306a36Sopenharmony_ci#define GPIO_BASE 0x048 3162306a36Sopenharmony_ci#define GPIO_CTRL 0x04c 3262306a36Sopenharmony_ci#define GPIO_EN 0x010 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* 3562306a36Sopenharmony_ci * The ICH7 GPIO register block is 64 bytes in size. 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci#define ICH7_GPIO_SIZE 64 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* 4062306a36Sopenharmony_ci * Define register offsets within the ICH7 register block. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci#define GPIO_USE_SEL 0x000 4362306a36Sopenharmony_ci#define GP_IO_SEL 0x004 4462306a36Sopenharmony_ci#define GP_LVL 0x00c 4562306a36Sopenharmony_ci#define GPO_BLINK 0x018 4662306a36Sopenharmony_ci#define GPI_INV 0x030 4762306a36Sopenharmony_ci#define GPIO_USE_SEL2 0x034 4862306a36Sopenharmony_ci#define GP_IO_SEL2 0x038 4962306a36Sopenharmony_ci#define GP_LVL2 0x03c 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/* 5262306a36Sopenharmony_ci * PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives. 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_cistatic const struct pci_device_id ich7_lpc_pci_id[] = { 5562306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) }, 5662306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) }, 5762306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) }, 5862306a36Sopenharmony_ci { } /* NULL entry */ 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, ich7_lpc_pci_id); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic int __init ss4200_led_dmi_callback(const struct dmi_system_id *id) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci pr_info("detected '%s'\n", id->ident); 6662306a36Sopenharmony_ci return 1; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic bool nodetect; 7062306a36Sopenharmony_cimodule_param_named(nodetect, nodetect, bool, 0); 7162306a36Sopenharmony_ciMODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection"); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci/* 7462306a36Sopenharmony_ci * struct nas_led_whitelist - List of known good models 7562306a36Sopenharmony_ci * 7662306a36Sopenharmony_ci * Contains the known good models this driver is compatible with. 7762306a36Sopenharmony_ci * When adding a new model try to be as strict as possible. This 7862306a36Sopenharmony_ci * makes it possible to keep the false positives (the model is 7962306a36Sopenharmony_ci * detected as working, but in reality it is not) as low as 8062306a36Sopenharmony_ci * possible. 8162306a36Sopenharmony_ci */ 8262306a36Sopenharmony_cistatic const struct dmi_system_id nas_led_whitelist[] __initconst = { 8362306a36Sopenharmony_ci { 8462306a36Sopenharmony_ci .callback = ss4200_led_dmi_callback, 8562306a36Sopenharmony_ci .ident = "Intel SS4200-E", 8662306a36Sopenharmony_ci .matches = { 8762306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Intel"), 8862306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "SS4200-E"), 8962306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00") 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci }, 9262306a36Sopenharmony_ci { 9362306a36Sopenharmony_ci /* 9462306a36Sopenharmony_ci * FUJITSU SIEMENS SCALEO Home Server/SS4200-E 9562306a36Sopenharmony_ci * BIOS V090L 12/19/2007 9662306a36Sopenharmony_ci */ 9762306a36Sopenharmony_ci .callback = ss4200_led_dmi_callback, 9862306a36Sopenharmony_ci .ident = "Fujitsu Siemens SCALEO Home Server", 9962306a36Sopenharmony_ci .matches = { 10062306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), 10162306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "SCALEO Home Server"), 10262306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00") 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci }, 10562306a36Sopenharmony_ci {} 10662306a36Sopenharmony_ci}; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/* 10962306a36Sopenharmony_ci * Base I/O address assigned to the Power Management register block 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_cistatic u32 g_pm_io_base; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* 11462306a36Sopenharmony_ci * Base I/O address assigned to the ICH7 GPIO register block 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistatic u32 nas_gpio_io_base; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci/* 11962306a36Sopenharmony_ci * When we successfully register a region, we are returned a resource. 12062306a36Sopenharmony_ci * We use these to identify which regions we need to release on our way 12162306a36Sopenharmony_ci * back out. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_cistatic struct resource *gp_gpio_resource; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistruct nasgpio_led { 12662306a36Sopenharmony_ci char *name; 12762306a36Sopenharmony_ci u32 gpio_bit; 12862306a36Sopenharmony_ci struct led_classdev led_cdev; 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/* 13262306a36Sopenharmony_ci * gpio_bit(s) are the ICH7 GPIO bit assignments 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_cistatic struct nasgpio_led nasgpio_leds[] = { 13562306a36Sopenharmony_ci { .name = "hdd1:blue:sata", .gpio_bit = 0 }, 13662306a36Sopenharmony_ci { .name = "hdd1:amber:sata", .gpio_bit = 1 }, 13762306a36Sopenharmony_ci { .name = "hdd2:blue:sata", .gpio_bit = 2 }, 13862306a36Sopenharmony_ci { .name = "hdd2:amber:sata", .gpio_bit = 3 }, 13962306a36Sopenharmony_ci { .name = "hdd3:blue:sata", .gpio_bit = 4 }, 14062306a36Sopenharmony_ci { .name = "hdd3:amber:sata", .gpio_bit = 5 }, 14162306a36Sopenharmony_ci { .name = "hdd4:blue:sata", .gpio_bit = 6 }, 14262306a36Sopenharmony_ci { .name = "hdd4:amber:sata", .gpio_bit = 7 }, 14362306a36Sopenharmony_ci { .name = "power:blue:power", .gpio_bit = 27}, 14462306a36Sopenharmony_ci { .name = "power:amber:power", .gpio_bit = 28}, 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci#define NAS_RECOVERY 0x00000400 /* GPIO10 */ 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic struct nasgpio_led * 15062306a36Sopenharmony_ciled_classdev_to_nasgpio_led(struct led_classdev *led_cdev) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci return container_of(led_cdev, struct nasgpio_led, led_cdev); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic struct nasgpio_led *get_led_named(char *name) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci int i; 15862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) { 15962306a36Sopenharmony_ci if (strcmp(nasgpio_leds[i].name, name)) 16062306a36Sopenharmony_ci continue; 16162306a36Sopenharmony_ci return &nasgpio_leds[i]; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci return NULL; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci/* 16762306a36Sopenharmony_ci * This protects access to the gpio ports. 16862306a36Sopenharmony_ci */ 16962306a36Sopenharmony_cistatic DEFINE_SPINLOCK(nasgpio_gpio_lock); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci/* 17262306a36Sopenharmony_ci * There are two gpio ports, one for blinking and the other 17362306a36Sopenharmony_ci * for power. @port tells us if we're doing blinking or 17462306a36Sopenharmony_ci * power control. 17562306a36Sopenharmony_ci * 17662306a36Sopenharmony_ci * Caller must hold nasgpio_gpio_lock 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_cistatic void __nasgpio_led_set_attr(struct led_classdev *led_cdev, 17962306a36Sopenharmony_ci u32 port, u32 value) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); 18262306a36Sopenharmony_ci u32 gpio_out; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci gpio_out = inl(nas_gpio_io_base + port); 18562306a36Sopenharmony_ci if (value) 18662306a36Sopenharmony_ci gpio_out |= (1<<led->gpio_bit); 18762306a36Sopenharmony_ci else 18862306a36Sopenharmony_ci gpio_out &= ~(1<<led->gpio_bit); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci outl(gpio_out, nas_gpio_io_base + port); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic void nasgpio_led_set_attr(struct led_classdev *led_cdev, 19462306a36Sopenharmony_ci u32 port, u32 value) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci spin_lock(&nasgpio_gpio_lock); 19762306a36Sopenharmony_ci __nasgpio_led_set_attr(led_cdev, port, value); 19862306a36Sopenharmony_ci spin_unlock(&nasgpio_gpio_lock); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); 20462306a36Sopenharmony_ci u32 gpio_in; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci spin_lock(&nasgpio_gpio_lock); 20762306a36Sopenharmony_ci gpio_in = inl(nas_gpio_io_base + port); 20862306a36Sopenharmony_ci spin_unlock(&nasgpio_gpio_lock); 20962306a36Sopenharmony_ci if (gpio_in & (1<<led->gpio_bit)) 21062306a36Sopenharmony_ci return 1; 21162306a36Sopenharmony_ci return 0; 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci/* 21562306a36Sopenharmony_ci * There is actual brightness control in the hardware, 21662306a36Sopenharmony_ci * but it is via smbus commands and not implemented 21762306a36Sopenharmony_ci * in this driver. 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_cistatic void nasgpio_led_set_brightness(struct led_classdev *led_cdev, 22062306a36Sopenharmony_ci enum led_brightness brightness) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci u32 setting = 0; 22362306a36Sopenharmony_ci if (brightness >= LED_HALF) 22462306a36Sopenharmony_ci setting = 1; 22562306a36Sopenharmony_ci /* 22662306a36Sopenharmony_ci * Hold the lock across both operations. This ensures 22762306a36Sopenharmony_ci * consistency so that both the "turn off blinking" 22862306a36Sopenharmony_ci * and "turn light off" operations complete as a set. 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ci spin_lock(&nasgpio_gpio_lock); 23162306a36Sopenharmony_ci /* 23262306a36Sopenharmony_ci * LED class documentation asks that past blink state 23362306a36Sopenharmony_ci * be disabled when brightness is turned to zero. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_ci if (brightness == 0) 23662306a36Sopenharmony_ci __nasgpio_led_set_attr(led_cdev, GPO_BLINK, 0); 23762306a36Sopenharmony_ci __nasgpio_led_set_attr(led_cdev, GP_LVL, setting); 23862306a36Sopenharmony_ci spin_unlock(&nasgpio_gpio_lock); 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic int nasgpio_led_set_blink(struct led_classdev *led_cdev, 24262306a36Sopenharmony_ci unsigned long *delay_on, 24362306a36Sopenharmony_ci unsigned long *delay_off) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci u32 setting = 1; 24662306a36Sopenharmony_ci if (!(*delay_on == 0 && *delay_off == 0) && 24762306a36Sopenharmony_ci !(*delay_on == 500 && *delay_off == 500)) 24862306a36Sopenharmony_ci return -EINVAL; 24962306a36Sopenharmony_ci /* 25062306a36Sopenharmony_ci * These are very approximate. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_ci *delay_on = 500; 25362306a36Sopenharmony_ci *delay_off = 500; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci nasgpio_led_set_attr(led_cdev, GPO_BLINK, setting); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci return 0; 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci/* 26262306a36Sopenharmony_ci * Initialize the ICH7 GPIO registers for NAS usage. The BIOS should have 26362306a36Sopenharmony_ci * already taken care of this, but we will do so in a non destructive manner 26462306a36Sopenharmony_ci * so that we have what we need whether the BIOS did it or not. 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_cistatic int ich7_gpio_init(struct device *dev) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci int i; 26962306a36Sopenharmony_ci u32 config_data = 0; 27062306a36Sopenharmony_ci u32 all_nas_led = 0; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) 27362306a36Sopenharmony_ci all_nas_led |= (1<<nasgpio_leds[i].gpio_bit); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci spin_lock(&nasgpio_gpio_lock); 27662306a36Sopenharmony_ci /* 27762306a36Sopenharmony_ci * We need to enable all of the GPIO lines used by the NAS box, 27862306a36Sopenharmony_ci * so we will read the current Use Selection and add our usage 27962306a36Sopenharmony_ci * to it. This should be benign with regard to the original 28062306a36Sopenharmony_ci * BIOS configuration. 28162306a36Sopenharmony_ci */ 28262306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GPIO_USE_SEL); 28362306a36Sopenharmony_ci dev_dbg(dev, ": Data read from GPIO_USE_SEL = 0x%08x\n", config_data); 28462306a36Sopenharmony_ci config_data |= all_nas_led + NAS_RECOVERY; 28562306a36Sopenharmony_ci outl(config_data, nas_gpio_io_base + GPIO_USE_SEL); 28662306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GPIO_USE_SEL); 28762306a36Sopenharmony_ci dev_dbg(dev, ": GPIO_USE_SEL = 0x%08x\n\n", config_data); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* 29062306a36Sopenharmony_ci * The LED GPIO outputs need to be configured for output, so we 29162306a36Sopenharmony_ci * will ensure that all LED lines are cleared for output and the 29262306a36Sopenharmony_ci * RECOVERY line ready for input. This too should be benign with 29362306a36Sopenharmony_ci * regard to BIOS configuration. 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GP_IO_SEL); 29662306a36Sopenharmony_ci dev_dbg(dev, ": Data read from GP_IO_SEL = 0x%08x\n", 29762306a36Sopenharmony_ci config_data); 29862306a36Sopenharmony_ci config_data &= ~all_nas_led; 29962306a36Sopenharmony_ci config_data |= NAS_RECOVERY; 30062306a36Sopenharmony_ci outl(config_data, nas_gpio_io_base + GP_IO_SEL); 30162306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GP_IO_SEL); 30262306a36Sopenharmony_ci dev_dbg(dev, ": GP_IO_SEL = 0x%08x\n", config_data); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci /* 30562306a36Sopenharmony_ci * In our final system, the BIOS will initialize the state of all 30662306a36Sopenharmony_ci * of the LEDs. For now, we turn them all off (or Low). 30762306a36Sopenharmony_ci */ 30862306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GP_LVL); 30962306a36Sopenharmony_ci dev_dbg(dev, ": Data read from GP_LVL = 0x%08x\n", config_data); 31062306a36Sopenharmony_ci /* 31162306a36Sopenharmony_ci * In our final system, the BIOS will initialize the blink state of all 31262306a36Sopenharmony_ci * of the LEDs. For now, we turn blink off for all of them. 31362306a36Sopenharmony_ci */ 31462306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GPO_BLINK); 31562306a36Sopenharmony_ci dev_dbg(dev, ": Data read from GPO_BLINK = 0x%08x\n", config_data); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci /* 31862306a36Sopenharmony_ci * At this moment, I am unsure if anything needs to happen with GPI_INV 31962306a36Sopenharmony_ci */ 32062306a36Sopenharmony_ci config_data = inl(nas_gpio_io_base + GPI_INV); 32162306a36Sopenharmony_ci dev_dbg(dev, ": Data read from GPI_INV = 0x%08x\n", config_data); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci spin_unlock(&nasgpio_gpio_lock); 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic void ich7_lpc_cleanup(struct device *dev) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci /* 33062306a36Sopenharmony_ci * If we were given exclusive use of the GPIO 33162306a36Sopenharmony_ci * I/O Address range, we must return it. 33262306a36Sopenharmony_ci */ 33362306a36Sopenharmony_ci if (gp_gpio_resource) { 33462306a36Sopenharmony_ci dev_dbg(dev, ": Releasing GPIO I/O addresses\n"); 33562306a36Sopenharmony_ci release_region(nas_gpio_io_base, ICH7_GPIO_SIZE); 33662306a36Sopenharmony_ci gp_gpio_resource = NULL; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci} 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci/* 34162306a36Sopenharmony_ci * The OS has determined that the LPC of the Intel ICH7 Southbridge is present 34262306a36Sopenharmony_ci * so we can retrive the required operational information and prepare the GPIO. 34362306a36Sopenharmony_ci */ 34462306a36Sopenharmony_cistatic struct pci_dev *nas_gpio_pci_dev; 34562306a36Sopenharmony_cistatic int ich7_lpc_probe(struct pci_dev *dev, 34662306a36Sopenharmony_ci const struct pci_device_id *id) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci int status; 34962306a36Sopenharmony_ci u32 gc = 0; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci status = pci_enable_device(dev); 35262306a36Sopenharmony_ci if (status) { 35362306a36Sopenharmony_ci dev_err(&dev->dev, "pci_enable_device failed\n"); 35462306a36Sopenharmony_ci return -EIO; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci nas_gpio_pci_dev = dev; 35862306a36Sopenharmony_ci status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base); 35962306a36Sopenharmony_ci if (status) 36062306a36Sopenharmony_ci goto out; 36162306a36Sopenharmony_ci g_pm_io_base &= 0x00000ff80; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci status = pci_read_config_dword(dev, GPIO_CTRL, &gc); 36462306a36Sopenharmony_ci if (!(GPIO_EN & gc)) { 36562306a36Sopenharmony_ci status = -EEXIST; 36662306a36Sopenharmony_ci dev_info(&dev->dev, 36762306a36Sopenharmony_ci "ERROR: The LPC GPIO Block has not been enabled.\n"); 36862306a36Sopenharmony_ci goto out; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base); 37262306a36Sopenharmony_ci if (0 > status) { 37362306a36Sopenharmony_ci dev_info(&dev->dev, "Unable to read GPIOBASE.\n"); 37462306a36Sopenharmony_ci goto out; 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base); 37762306a36Sopenharmony_ci nas_gpio_io_base &= 0x00000ffc0; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* 38062306a36Sopenharmony_ci * Insure that we have exclusive access to the GPIO I/O address range. 38162306a36Sopenharmony_ci */ 38262306a36Sopenharmony_ci gp_gpio_resource = request_region(nas_gpio_io_base, ICH7_GPIO_SIZE, 38362306a36Sopenharmony_ci KBUILD_MODNAME); 38462306a36Sopenharmony_ci if (NULL == gp_gpio_resource) { 38562306a36Sopenharmony_ci dev_info(&dev->dev, 38662306a36Sopenharmony_ci "ERROR Unable to register GPIO I/O addresses.\n"); 38762306a36Sopenharmony_ci status = -1; 38862306a36Sopenharmony_ci goto out; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci /* 39262306a36Sopenharmony_ci * Initialize the GPIO for NAS/Home Server Use 39362306a36Sopenharmony_ci */ 39462306a36Sopenharmony_ci ich7_gpio_init(&dev->dev); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ciout: 39762306a36Sopenharmony_ci if (status) { 39862306a36Sopenharmony_ci ich7_lpc_cleanup(&dev->dev); 39962306a36Sopenharmony_ci pci_disable_device(dev); 40062306a36Sopenharmony_ci } 40162306a36Sopenharmony_ci return status; 40262306a36Sopenharmony_ci} 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cistatic void ich7_lpc_remove(struct pci_dev *dev) 40562306a36Sopenharmony_ci{ 40662306a36Sopenharmony_ci ich7_lpc_cleanup(&dev->dev); 40762306a36Sopenharmony_ci pci_disable_device(dev); 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci/* 41162306a36Sopenharmony_ci * pci_driver structure passed to the PCI modules 41262306a36Sopenharmony_ci */ 41362306a36Sopenharmony_cistatic struct pci_driver nas_gpio_pci_driver = { 41462306a36Sopenharmony_ci .name = KBUILD_MODNAME, 41562306a36Sopenharmony_ci .id_table = ich7_lpc_pci_id, 41662306a36Sopenharmony_ci .probe = ich7_lpc_probe, 41762306a36Sopenharmony_ci .remove = ich7_lpc_remove, 41862306a36Sopenharmony_ci}; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_cistatic struct led_classdev *get_classdev_for_led_nr(int nr) 42162306a36Sopenharmony_ci{ 42262306a36Sopenharmony_ci struct nasgpio_led *nas_led = &nasgpio_leds[nr]; 42362306a36Sopenharmony_ci struct led_classdev *led = &nas_led->led_cdev; 42462306a36Sopenharmony_ci return led; 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic void set_power_light_amber_noblink(void) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct nasgpio_led *amber = get_led_named("power:amber:power"); 43162306a36Sopenharmony_ci struct nasgpio_led *blue = get_led_named("power:blue:power"); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (!amber || !blue) 43462306a36Sopenharmony_ci return; 43562306a36Sopenharmony_ci /* 43662306a36Sopenharmony_ci * LED_OFF implies disabling future blinking 43762306a36Sopenharmony_ci */ 43862306a36Sopenharmony_ci pr_debug("setting blue off and amber on\n"); 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci nasgpio_led_set_brightness(&blue->led_cdev, LED_OFF); 44162306a36Sopenharmony_ci nasgpio_led_set_brightness(&amber->led_cdev, LED_FULL); 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic ssize_t blink_show(struct device *dev, 44562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci struct led_classdev *led = dev_get_drvdata(dev); 44862306a36Sopenharmony_ci int blinking = 0; 44962306a36Sopenharmony_ci if (nasgpio_led_get_attr(led, GPO_BLINK)) 45062306a36Sopenharmony_ci blinking = 1; 45162306a36Sopenharmony_ci return sprintf(buf, "%u\n", blinking); 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic ssize_t blink_store(struct device *dev, 45562306a36Sopenharmony_ci struct device_attribute *attr, 45662306a36Sopenharmony_ci const char *buf, size_t size) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci int ret; 45962306a36Sopenharmony_ci struct led_classdev *led = dev_get_drvdata(dev); 46062306a36Sopenharmony_ci unsigned long blink_state; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci ret = kstrtoul(buf, 10, &blink_state); 46362306a36Sopenharmony_ci if (ret) 46462306a36Sopenharmony_ci return ret; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci nasgpio_led_set_attr(led, GPO_BLINK, blink_state); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci return size; 46962306a36Sopenharmony_ci} 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(blink); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cistatic struct attribute *nasgpio_led_attrs[] = { 47462306a36Sopenharmony_ci &dev_attr_blink.attr, 47562306a36Sopenharmony_ci NULL 47662306a36Sopenharmony_ci}; 47762306a36Sopenharmony_ciATTRIBUTE_GROUPS(nasgpio_led); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic int register_nasgpio_led(int led_nr) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct nasgpio_led *nas_led = &nasgpio_leds[led_nr]; 48262306a36Sopenharmony_ci struct led_classdev *led = get_classdev_for_led_nr(led_nr); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci led->name = nas_led->name; 48562306a36Sopenharmony_ci led->brightness = LED_OFF; 48662306a36Sopenharmony_ci if (nasgpio_led_get_attr(led, GP_LVL)) 48762306a36Sopenharmony_ci led->brightness = LED_FULL; 48862306a36Sopenharmony_ci led->brightness_set = nasgpio_led_set_brightness; 48962306a36Sopenharmony_ci led->blink_set = nasgpio_led_set_blink; 49062306a36Sopenharmony_ci led->groups = nasgpio_led_groups; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci return led_classdev_register(&nas_gpio_pci_dev->dev, led); 49362306a36Sopenharmony_ci} 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_cistatic void unregister_nasgpio_led(int led_nr) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci struct led_classdev *led = get_classdev_for_led_nr(led_nr); 49862306a36Sopenharmony_ci led_classdev_unregister(led); 49962306a36Sopenharmony_ci} 50062306a36Sopenharmony_ci/* 50162306a36Sopenharmony_ci * module load/initialization 50262306a36Sopenharmony_ci */ 50362306a36Sopenharmony_cistatic int __init nas_gpio_init(void) 50462306a36Sopenharmony_ci{ 50562306a36Sopenharmony_ci int i; 50662306a36Sopenharmony_ci int ret = 0; 50762306a36Sopenharmony_ci int nr_devices = 0; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci nr_devices = dmi_check_system(nas_led_whitelist); 51062306a36Sopenharmony_ci if (nodetect) { 51162306a36Sopenharmony_ci pr_info("skipping hardware autodetection\n"); 51262306a36Sopenharmony_ci pr_info("Please send 'dmidecode' output to dave@sr71.net\n"); 51362306a36Sopenharmony_ci nr_devices++; 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci if (nr_devices <= 0) { 51762306a36Sopenharmony_ci pr_info("no LED devices found\n"); 51862306a36Sopenharmony_ci return -ENODEV; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci pr_info("registering PCI driver\n"); 52262306a36Sopenharmony_ci ret = pci_register_driver(&nas_gpio_pci_driver); 52362306a36Sopenharmony_ci if (ret) 52462306a36Sopenharmony_ci return ret; 52562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) { 52662306a36Sopenharmony_ci ret = register_nasgpio_led(i); 52762306a36Sopenharmony_ci if (ret) 52862306a36Sopenharmony_ci goto out_err; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci /* 53162306a36Sopenharmony_ci * When the system powers on, the BIOS leaves the power 53262306a36Sopenharmony_ci * light blue and blinking. This will turn it solid 53362306a36Sopenharmony_ci * amber once the driver is loaded. 53462306a36Sopenharmony_ci */ 53562306a36Sopenharmony_ci set_power_light_amber_noblink(); 53662306a36Sopenharmony_ci return 0; 53762306a36Sopenharmony_ciout_err: 53862306a36Sopenharmony_ci for (i--; i >= 0; i--) 53962306a36Sopenharmony_ci unregister_nasgpio_led(i); 54062306a36Sopenharmony_ci pci_unregister_driver(&nas_gpio_pci_driver); 54162306a36Sopenharmony_ci return ret; 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci/* 54562306a36Sopenharmony_ci * module unload 54662306a36Sopenharmony_ci */ 54762306a36Sopenharmony_cistatic void __exit nas_gpio_exit(void) 54862306a36Sopenharmony_ci{ 54962306a36Sopenharmony_ci int i; 55062306a36Sopenharmony_ci pr_info("Unregistering driver\n"); 55162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) 55262306a36Sopenharmony_ci unregister_nasgpio_led(i); 55362306a36Sopenharmony_ci pci_unregister_driver(&nas_gpio_pci_driver); 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_cimodule_init(nas_gpio_init); 55762306a36Sopenharmony_cimodule_exit(nas_gpio_exit); 558