// SPDX-License-Identifier: GPL-2.0-only
/*
* SS4200 - E Hardware API
* Copyright ( c ) 2009 , Intel Corporation .
* Copyright IBM Corporation , 2009
*
* Author : Dave Hansen < dave @ sr71 . net >
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/uaccess.h>
MODULE_AUTHOR("Rodney Girod <rgirod@confocus.com>, Dave Hansen <dave@sr71.net>" );
MODULE_DESCRIPTION("Intel NAS/Home Server ICH7 GPIO Driver" );
MODULE_LICENSE("GPL" );
/*
* ICH7 LPC / GPIO PCI Config register offsets
*/
#define PMBASE 0 x040
#define GPIO_BASE 0 x048
#define GPIO_CTRL 0 x04c
#define GPIO_EN 0 x010
/*
* The ICH7 GPIO register block is 64 bytes in size .
*/
#define ICH7_GPIO_SIZE 64
/*
* Define register offsets within the ICH7 register block .
*/
#define GPIO_USE_SEL 0 x000
#define GP_IO_SEL 0 x004
#define GP_LVL 0 x00c
#define GPO_BLINK 0 x018
#define GPI_INV 0 x030
#define GPIO_USE_SEL2 0 x034
#define GP_IO_SEL2 0 x038
#define GP_LVL2 0 x03c
/*
* PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives .
*/
static const struct pci_device_id ich7_lpc_pci_id[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) },
{ } /* NULL entry */
};
MODULE_DEVICE_TABLE(pci, ich7_lpc_pci_id);
static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id)
{
pr_info("detected '%s'\n" , id->ident);
return 1 ;
}
static bool nodetect;
module_param_named(nodetect, nodetect, bool , 0 );
MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection" );
/*
* struct nas_led_whitelist - List of known good models
*
* Contains the known good models this driver is compatible with .
* When adding a new model try to be as strict as possible . This
* makes it possible to keep the false positives ( the model is
* detected as working , but in reality it is not ) as low as
* possible .
*/
static const struct dmi_system_id nas_led_whitelist[] __initconst = {
{
.callback = ss4200_led_dmi_callback,
.ident = "Intel SS4200-E" ,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel" ),
DMI_MATCH(DMI_PRODUCT_NAME, "SS4200-E" ),
DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00" )
}
},
{
/*
* FUJITSU SIEMENS SCALEO Home Server / SS4200 - E
* BIOS V090L 12 / 19 / 2007
*/
.callback = ss4200_led_dmi_callback,
.ident = "Fujitsu Siemens SCALEO Home Server" ,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS" ),
DMI_MATCH(DMI_PRODUCT_NAME, "SCALEO Home Server" ),
DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00" )
}
},
{}
};
/*
* Base I / O address assigned to the Power Management register block
*/
static u32 g_pm_io_base;
/*
* Base I / O address assigned to the ICH7 GPIO register block
*/
static u32 nas_gpio_io_base;
/*
* When we successfully register a region , we are returned a resource .
* We use these to identify which regions we need to release on our way
* back out .
*/
static struct resource *gp_gpio_resource;
struct nasgpio_led {
char *name;
u32 gpio_bit;
struct led_classdev led_cdev;
};
/*
* gpio_bit ( s ) are the ICH7 GPIO bit assignments
*/
static struct nasgpio_led nasgpio_leds[] = {
{ .name = "hdd1:blue:sata" , .gpio_bit = 0 },
{ .name = "hdd1:amber:sata" , .gpio_bit = 1 },
{ .name = "hdd2:blue:sata" , .gpio_bit = 2 },
{ .name = "hdd2:amber:sata" , .gpio_bit = 3 },
{ .name = "hdd3:blue:sata" , .gpio_bit = 4 },
{ .name = "hdd3:amber:sata" , .gpio_bit = 5 },
{ .name = "hdd4:blue:sata" , .gpio_bit = 6 },
{ .name = "hdd4:amber:sata" , .gpio_bit = 7 },
{ .name = "power:blue:power" , .gpio_bit = 27 },
{ .name = "power:amber:power" , .gpio_bit = 28 },
};
#define NAS_RECOVERY 0 x00000400 /* GPIO10 */
static struct nasgpio_led *
led_classdev_to_nasgpio_led(struct led_classdev *led_cdev)
{
return container_of(led_cdev, struct nasgpio_led, led_cdev);
}
static struct nasgpio_led *get_led_named(char *name)
{
int i;
for (i = 0 ; i < ARRAY_SIZE(nasgpio_leds); i++) {
if (strcmp(nasgpio_leds[i].name, name))
continue ;
return &nasgpio_leds[i];
}
return NULL;
}
/*
* This protects access to the gpio ports .
*/
static DEFINE_SPINLOCK(nasgpio_gpio_lock);
/*
* There are two gpio ports , one for blinking and the other
* for power . @ port tells us if we ' re doing blinking or
* power control .
*
* Caller must hold nasgpio_gpio_lock
*/
static void __nasgpio_led_set_attr(struct led_classdev *led_cdev,
u32 port, u32 value)
{
struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev);
u32 gpio_out;
gpio_out = inl(nas_gpio_io_base + port);
if (value)
gpio_out |= (1 <<led->gpio_bit);
else
gpio_out &= ~(1 <<led->gpio_bit);
outl(gpio_out, nas_gpio_io_base + port);
}
static void nasgpio_led_set_attr(struct led_classdev *led_cdev,
u32 port, u32 value)
{
spin_lock(&nasgpio_gpio_lock);
__nasgpio_led_set_attr(led_cdev, port, value);
spin_unlock(&nasgpio_gpio_lock);
}
static u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port)
{
struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev);
u32 gpio_in;
spin_lock(&nasgpio_gpio_lock);
gpio_in = inl(nas_gpio_io_base + port);
spin_unlock(&nasgpio_gpio_lock);
if (gpio_in & (1 <<led->gpio_bit))
return 1 ;
return 0 ;
}
/*
* There is actual brightness control in the hardware ,
* but it is via smbus commands and not implemented
* in this driver .
*/
static void nasgpio_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
u32 setting = 0 ;
if (brightness >= LED_HALF)
setting = 1 ;
/*
* Hold the lock across both operations . This ensures
* consistency so that both the " turn off blinking "
* and " turn light off " operations complete as a set .
*/
spin_lock(&nasgpio_gpio_lock);
/*
* LED class documentation asks that past blink state
* be disabled when brightness is turned to zero .
*/
if (brightness == 0 )
__nasgpio_led_set_attr(led_cdev, GPO_BLINK, 0 );
__nasgpio_led_set_attr(led_cdev, GP_LVL, setting);
spin_unlock(&nasgpio_gpio_lock);
}
static int nasgpio_led_set_blink(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
u32 setting = 1 ;
if (!(*delay_on == 0 && *delay_off == 0 ) &&
!(*delay_on == 500 && *delay_off == 500 ))
return -EINVAL;
/*
* These are very approximate .
*/
*delay_on = 500 ;
*delay_off = 500 ;
nasgpio_led_set_attr(led_cdev, GPO_BLINK, setting);
return 0 ;
}
/*
* Initialize the ICH7 GPIO registers for NAS usage . The BIOS should have
* already taken care of this , but we will do so in a non destructive manner
* so that we have what we need whether the BIOS did it or not .
*/
static int ich7_gpio_init(struct device *dev)
{
int i;
u32 config_data = 0 ;
u32 all_nas_led = 0 ;
for (i = 0 ; i < ARRAY_SIZE(nasgpio_leds); i++)
all_nas_led |= (1 <<nasgpio_leds[i].gpio_bit);
spin_lock(&nasgpio_gpio_lock);
/*
* We need to enable all of the GPIO lines used by the NAS box ,
* so we will read the current Use Selection and add our usage
* to it . This should be benign with regard to the original
* BIOS configuration .
*/
config_data = inl(nas_gpio_io_base + GPIO_USE_SEL);
dev_dbg(dev, ": Data read from GPIO_USE_SEL = 0x%08x\n" , config_data);
config_data |= all_nas_led + NAS_RECOVERY;
outl(config_data, nas_gpio_io_base + GPIO_USE_SEL);
config_data = inl(nas_gpio_io_base + GPIO_USE_SEL);
dev_dbg(dev, ": GPIO_USE_SEL = 0x%08x\n\n" , config_data);
/*
* The LED GPIO outputs need to be configured for output , so we
* will ensure that all LED lines are cleared for output and the
* RECOVERY line ready for input . This too should be benign with
* regard to BIOS configuration .
*/
config_data = inl(nas_gpio_io_base + GP_IO_SEL);
dev_dbg(dev, ": Data read from GP_IO_SEL = 0x%08x\n" ,
config_data);
config_data &= ~all_nas_led;
config_data |= NAS_RECOVERY;
outl(config_data, nas_gpio_io_base + GP_IO_SEL);
config_data = inl(nas_gpio_io_base + GP_IO_SEL);
dev_dbg(dev, ": GP_IO_SEL = 0x%08x\n" , config_data);
/*
* In our final system , the BIOS will initialize the state of all
* of the LEDs . For now , we turn them all off ( or Low ) .
*/
config_data = inl(nas_gpio_io_base + GP_LVL);
dev_dbg(dev, ": Data read from GP_LVL = 0x%08x\n" , config_data);
/*
* In our final system , the BIOS will initialize the blink state of all
* of the LEDs . For now , we turn blink off for all of them .
*/
config_data = inl(nas_gpio_io_base + GPO_BLINK);
dev_dbg(dev, ": Data read from GPO_BLINK = 0x%08x\n" , config_data);
/*
* At this moment , I am unsure if anything needs to happen with GPI_INV
*/
config_data = inl(nas_gpio_io_base + GPI_INV);
dev_dbg(dev, ": Data read from GPI_INV = 0x%08x\n" , config_data);
spin_unlock(&nasgpio_gpio_lock);
return 0 ;
}
static void ich7_lpc_cleanup(struct device *dev)
{
/*
* If we were given exclusive use of the GPIO
* I / O Address range , we must return it .
*/
if (gp_gpio_resource) {
dev_dbg(dev, ": Releasing GPIO I/O addresses\n" );
release_region(nas_gpio_io_base, ICH7_GPIO_SIZE);
gp_gpio_resource = NULL;
}
}
/*
* The OS has determined that the LPC of the Intel ICH7 Southbridge is present
* so we can retrive the required operational information and prepare the GPIO .
*/
static struct pci_dev *nas_gpio_pci_dev;
static int ich7_lpc_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
int status;
u32 gc = 0 ;
status = pci_enable_device(dev);
if (status) {
dev_err(&dev->dev, "pci_enable_device failed\n" );
return -EIO;
}
nas_gpio_pci_dev = dev;
status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base);
if (status) {
status = pcibios_err_to_errno(status);
goto out;
}
g_pm_io_base &= 0 x00000ff80;
status = pci_read_config_dword(dev, GPIO_CTRL, &gc);
if (!(GPIO_EN & gc)) {
status = -EEXIST;
dev_info(&dev->dev,
"ERROR: The LPC GPIO Block has not been enabled.\n" );
goto out;
}
status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base);
if (status) {
dev_info(&dev->dev, "Unable to read GPIOBASE.\n" );
status = pcibios_err_to_errno(status);
goto out;
}
dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n" , nas_gpio_io_base);
nas_gpio_io_base &= 0 x00000ffc0;
/*
* Insure that we have exclusive access to the GPIO I / O address range .
*/
gp_gpio_resource = request_region(nas_gpio_io_base, ICH7_GPIO_SIZE,
KBUILD_MODNAME);
if (NULL == gp_gpio_resource) {
dev_info(&dev->dev,
"ERROR Unable to register GPIO I/O addresses.\n" );
status = -1 ;
goto out;
}
/*
* Initialize the GPIO for NAS / Home Server Use
*/
ich7_gpio_init(&dev->dev);
out:
if (status) {
ich7_lpc_cleanup(&dev->dev);
pci_disable_device(dev);
}
return status;
}
static void ich7_lpc_remove(struct pci_dev *dev)
{
ich7_lpc_cleanup(&dev->dev);
pci_disable_device(dev);
}
/*
* pci_driver structure passed to the PCI modules
*/
static struct pci_driver nas_gpio_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = ich7_lpc_pci_id,
.probe = ich7_lpc_probe,
.remove = ich7_lpc_remove,
};
static struct led_classdev *get_classdev_for_led_nr(int nr)
{
struct nasgpio_led *nas_led = &nasgpio_leds[nr];
struct led_classdev *led = &nas_led->led_cdev;
return led;
}
static void set_power_light_amber_noblink(void )
{
struct nasgpio_led *amber = get_led_named("power:amber:power" );
struct nasgpio_led *blue = get_led_named("power:blue:power" );
if (!amber || !blue)
return ;
/*
* LED_OFF implies disabling future blinking
*/
pr_debug("setting blue off and amber on\n" );
nasgpio_led_set_brightness(&blue->led_cdev, LED_OFF);
nasgpio_led_set_brightness(&amber->led_cdev, LED_FULL);
}
static ssize_t blink_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led = dev_get_drvdata(dev);
int blinking = 0 ;
if (nasgpio_led_get_attr(led, GPO_BLINK))
blinking = 1 ;
return sprintf(buf, "%d\n" , blinking);
}
static ssize_t blink_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
int ret;
struct led_classdev *led = dev_get_drvdata(dev);
unsigned long blink_state;
ret = kstrtoul(buf, 10 , &blink_state);
if (ret)
return ret;
nasgpio_led_set_attr(led, GPO_BLINK, blink_state);
return size;
}
static DEVICE_ATTR_RW(blink);
static struct attribute *nasgpio_led_attrs[] = {
&dev_attr_blink.attr,
NULL
};
ATTRIBUTE_GROUPS(nasgpio_led);
static int register_nasgpio_led(int led_nr)
{
struct nasgpio_led *nas_led = &nasgpio_leds[led_nr];
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
led->name = nas_led->name;
led->brightness = LED_OFF;
if (nasgpio_led_get_attr(led, GP_LVL))
led->brightness = LED_FULL;
led->brightness_set = nasgpio_led_set_brightness;
led->blink_set = nasgpio_led_set_blink;
led->groups = nasgpio_led_groups;
return led_classdev_register(&nas_gpio_pci_dev->dev, led);
}
static void unregister_nasgpio_led(int led_nr)
{
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
led_classdev_unregister(led);
}
/*
* module load / initialization
*/
static int __init nas_gpio_init(void )
{
int i;
int ret = 0 ;
int nr_devices = 0 ;
nr_devices = dmi_check_system(nas_led_whitelist);
if (nodetect) {
pr_info("skipping hardware autodetection\n" );
pr_info("Please send 'dmidecode' output to dave@sr71.net\n" );
nr_devices++;
}
if (nr_devices <= 0 ) {
pr_info("no LED devices found\n" );
return -ENODEV;
}
pr_info("registering PCI driver\n" );
ret = pci_register_driver(&nas_gpio_pci_driver);
if (ret)
return ret;
for (i = 0 ; i < ARRAY_SIZE(nasgpio_leds); i++) {
ret = register_nasgpio_led(i);
if (ret)
goto out_err;
}
/*
* When the system powers on , the BIOS leaves the power
* light blue and blinking . This will turn it solid
* amber once the driver is loaded .
*/
set_power_light_amber_noblink();
return 0 ;
out_err:
for (i--; i >= 0 ; i--)
unregister_nasgpio_led(i);
pci_unregister_driver(&nas_gpio_pci_driver);
return ret;
}
/*
* module unload
*/
static void __exit nas_gpio_exit(void )
{
int i;
pr_info("Unregistering driver\n" );
for (i = 0 ; i < ARRAY_SIZE(nasgpio_leds); i++)
unregister_nasgpio_led(i);
pci_unregister_driver(&nas_gpio_pci_driver);
}
module_init(nas_gpio_init);
module_exit(nas_gpio_exit);
Messung V0.5 in Prozent C=95 H=92 G=93
¤ Dauer der Verarbeitung: 0.11 Sekunden
¤
*© Formatika GbR, Deutschland