I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
has an ICH10, I've added support for older ICH chips in case someone
needs it, as it was relatively simply to do that.
Signed-off-by: Jean Delvare <[email protected]>
Cc: Grant Likely <[email protected]>
---
Note 1: On early ICH chips, some pins are exclusively inputs or
outputs. The driver doesn't currently enforce this.
Note 2: I'm not yet sure if we want a module alias for this driver.
Many systems have the device but only a few of them will need the
driver (and an ACPI resource conflict will be reported for many
others, especially laptops I suspect.) So it might make more sense to
let consumer drivers request the i801_gpio driver as needed (which they
should do anyway, as you can't assume udev is always up and running on
all systems.)
Note 3: This is my first GPIO driver, so while it works fine for me, it
might not be perfect. I welcome comments on how to improve it.
drivers/gpio/Kconfig | 7
drivers/gpio/Makefile | 1
drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 440 insertions(+)
--- linux-2.6.39-rc3.orig/drivers/gpio/Kconfig 2011-04-18 19:41:51.000000000 +0200
+++ linux-2.6.39-rc3/drivers/gpio/Kconfig 2011-04-18 22:36:41.000000000 +0200
@@ -322,6 +322,13 @@ config GPIO_BT8XX
If unsure, say N.
+config GPIO_I801
+ tristate "Intel 82801 (ICH) GPIO"
+ depends on PCI
+ help
+ This driver is for the GPIO pins of Intel 82801 south bridges
+ (aka ICH).
+
config GPIO_LANGWELL
bool "Intel Langwell/Penwell GPIO support"
depends on PCI && X86
--- linux-2.6.39-rc3.orig/drivers/gpio/Makefile 2011-04-18 19:41:51.000000000 +0200
+++ linux-2.6.39-rc3/drivers/gpio/Makefile 2011-04-18 22:36:41.000000000 +0200
@@ -11,6 +11,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o
obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o
obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o
obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o
+obj-$(CONFIG_GPIO_I801) += i801_gpio.o
obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o
obj-$(CONFIG_GPIO_MAX730X) += max730x.o
obj-$(CONFIG_GPIO_MAX7300) += max7300.o
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.39-rc3/drivers/gpio/i801_gpio.c 2011-04-19 13:55:25.000000000 +0200
@@ -0,0 +1,432 @@
+/*
+ * Linux kernel driver for the Intel 82801 GPIO pins
+ * Copyright (C) 2011 Jean Delvare <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+
+static int force;
+module_param(force, int, 0444);
+MODULE_PARM_DESC(force, "Forcibly enable access to GPIO");
+
+enum chips { ICH2, ICH4, ICH6, ICH10_CORP };
+
+/* Register definitions */
+static const u8 REG_GPIO_USE_SEL[3] = { 0x00, 0x30, 0x40 };
+static const u8 REG_GP_IO_SEL[3] = { 0x04, 0x34, 0x44 };
+static const u8 REG_GP_LVL[3] = { 0x0C, 0x38, 0x48 };
+
+/**
+ * struct i801_gpio_data - 82801 GPIO private data
+ * @base: Base I/O port
+ * @io_size: I/O region size (64 or 128)
+ * @gpio: Data for GPIO infrastructure
+ * @lock: Mutex to serialize read-modify-write cycles
+ */
+struct i801_gpio_data {
+ u32 base;
+ u32 io_size;
+ u32 use_sel[3];
+ struct gpio_chip gpio;
+ struct mutex lock;
+};
+
+static int i801_gpio_request(struct gpio_chip *gpio, unsigned nr)
+{
+ struct i801_gpio_data *data;
+ int group = nr >> 5;
+
+ data = container_of(gpio, struct i801_gpio_data, gpio);
+ nr &= 0x1f;
+
+ if (data->use_sel[group] & BIT(nr))
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static void i801_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
+{
+ struct i801_gpio_data *data;
+ int group = nr >> 5;
+ u32 reg_val;
+
+ data = container_of(gpio, struct i801_gpio_data, gpio);
+ nr &= 0x1f;
+
+ mutex_lock(&data->lock);
+ reg_val = inl(data->base + REG_GP_LVL[group]);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+ outl(reg_val, data->base + REG_GP_LVL[group]);
+ mutex_unlock(&data->lock);
+}
+
+static int i801_gpio_get(struct gpio_chip *gpio, unsigned nr)
+{
+ struct i801_gpio_data *data;
+ int group = nr >> 5;
+
+ data = container_of(gpio, struct i801_gpio_data, gpio);
+ nr &= 0x1f;
+
+ return (inl(data->base + REG_GP_LVL[group]) >> nr) & 1;
+}
+
+static int i801_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
+ int val)
+{
+ struct i801_gpio_data *data;
+ int group = nr >> 5;
+ u32 reg_val;
+
+ data = container_of(gpio, struct i801_gpio_data, gpio);
+ nr &= 0x1f;
+
+ mutex_lock(&data->lock);
+ reg_val = inl(data->base + REG_GP_IO_SEL[group]);
+ reg_val &= ~BIT(nr);
+ outl(reg_val, data->base + REG_GP_IO_SEL[group]);
+
+ reg_val = inl(data->base + REG_GP_LVL[group]);
+ if (val)
+ reg_val |= BIT(nr);
+ else
+ reg_val &= ~BIT(nr);
+ outl(reg_val, data->base + REG_GP_LVL[group]);
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+static int i801_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ struct i801_gpio_data *data;
+ int group = nr >> 5;
+ u32 reg_val;
+
+ data = container_of(gpio, struct i801_gpio_data, gpio);
+ nr &= 0x1f;
+
+ mutex_lock(&data->lock);
+ reg_val = inl(data->base + REG_GP_IO_SEL[group]);
+ reg_val |= BIT(nr);
+ outl(reg_val, data->base + REG_GP_IO_SEL[group]);
+ mutex_unlock(&data->lock);
+
+ return 0;
+}
+
+static void __devinit i801_gpio_setup(struct gpio_chip *gpio,
+ struct device *dev, int ngpio)
+{
+ gpio->label = "i801_gpio";
+ gpio->dev = dev;
+ gpio->owner = THIS_MODULE;
+ gpio->request = i801_gpio_request;
+ gpio->direction_input = i801_gpio_direction_input;
+ gpio->get = i801_gpio_get;
+ gpio->direction_output = i801_gpio_direction_output;
+ gpio->set = i801_gpio_set;
+ gpio->base = -1;
+ gpio->ngpio = ngpio;
+}
+
+/*
+ * On the ICH/ICH0, ICH3, ICH4 and ICH5, some selection bits control more
+ * than one GPIO.
+ */
+static void __devinit i801_gpio_sel_fixup(struct i801_gpio_data *data,
+ u32 device)
+{
+ switch (device) {
+ case PCI_DEVICE_ID_INTEL_82801AA_0:
+ case PCI_DEVICE_ID_INTEL_82801AB_0:
+ case PCI_DEVICE_ID_INTEL_82801CA_0:
+ case PCI_DEVICE_ID_INTEL_82801CA_12:
+ case PCI_DEVICE_ID_INTEL_82801DB_0:
+ case PCI_DEVICE_ID_INTEL_82801DB_12:
+ case PCI_DEVICE_ID_INTEL_82801EB_0:
+ if (data->use_sel[0] & BIT(0))
+ data->use_sel[0] |= BIT(16);
+ if (data->use_sel[0] & BIT(1))
+ data->use_sel[0] |= BIT(17);
+ if (data->use_sel[0] & BIT(27))
+ data->use_sel[0] |= BIT(28);
+ break;
+ }
+}
+
+static __devinitdata
+const struct {
+ int ngroup;
+ int io_size;
+ u8 reg_gpiobase;
+ u8 reg_gc;
+} i801_gpio_cfg[] = {
+ [ICH2] = { 1, 64, 0x58, 0x5C },
+ [ICH4] = { 2, 64, 0x58, 0x5C },
+ [ICH6] = { 2, 64, 0x48, 0x4C },
+ [ICH10_CORP] = { 3, 128, 0x48, 0x4C },
+};
+
+static void __devinit i801_gpio_print_state(struct i801_gpio_data *data,
+ struct device *dev, int ngroup)
+{
+ int i, group;
+ u32 io_sel, lvl;
+
+ dev_dbg(dev, "Current state of GPIO pins:\n");
+ for (group = 0; group < ngroup; group++) {
+ io_sel = inl(data->base + REG_GP_IO_SEL[group]);
+ lvl = inl(data->base + REG_GP_LVL[group]);
+
+ for (i = 0; i < 32; i++) {
+ if (!(data->use_sel[group] & BIT(i)))
+ continue;
+
+ dev_dbg(dev, "GPIO%d: %s, level %d\n", group * 32 + i,
+ io_sel & BIT(i) ? "input" : "output",
+ !!(lvl & BIT(i)));
+ }
+ }
+}
+
+static int __devinit i801_gpio_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct i801_gpio_data *data;
+ u32 gpiobase;
+ u8 gc;
+ int type = id->driver_data;
+ int group, ngroup, err;
+
+ data = kzalloc(sizeof(struct i801_gpio_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to enable device (%d)\n", err);
+ goto err_free;
+ }
+
+ /* Get the base I/O address */
+ err = pci_read_config_dword(pdev, i801_gpio_cfg[type].reg_gpiobase,
+ &gpiobase);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
+ "GPIOBASE", err);
+ goto err_disable;
+ }
+ data->base = gpiobase & ~BIT(0);
+ if (!data->base) {
+ dev_err(&pdev->dev, "GPIOBASE not set\n");
+ err = -ENODEV;
+ goto err_disable;
+ }
+
+ /* Check configuration */
+ err = pci_read_config_byte(pdev, i801_gpio_cfg[type].reg_gc, &gc);
+ if (err) {
+ dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
+ "GC", err);
+ goto err_disable;
+ }
+ if (gc & BIT(0)) {
+ dev_err(&pdev->dev, "GPIO function is %s\n", "locked");
+ err = -EBUSY;
+ goto err_disable;
+ }
+ if (!(gc & BIT(4))) {
+ if (!force) {
+ dev_err(&pdev->dev, "GPIO function is %s\n",
+ "disabled");
+ err = -ENODEV;
+ goto err_disable;
+ }
+
+ gc |= BIT(4);
+ err = pci_write_config_byte(pdev,
+ i801_gpio_cfg[type].reg_gc, gc);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Failed to enable GPIO function (%d)\n",
+ err);
+ err = -ENODEV;
+ goto err_disable;
+ }
+ dev_info(&pdev->dev, "Enabling GPIO function\n");
+ }
+
+ /*
+ * "Corporate" incarnations of the ICH10 have an I/O region of size
+ * 128 and 73 GPIO pins. Others ("consumer") have an I/O region of
+ * size only 64 and 61 GPIO pins, as the ICH6, ICH7, ICH8 and ICH9
+ * had.
+ */
+ data->io_size = i801_gpio_cfg[type].io_size;
+ if (!force) {
+ err = acpi_check_region(data->base, data->io_size, "i801_gpio");
+ if (err)
+ goto err_disable;
+ }
+ if (!request_region(data->base, data->io_size, "i801_gpio")) {
+ dev_err(&pdev->dev, "Failed to request I/O ports (%d)", err);
+ err = -EBUSY;
+ goto err_disable;
+ }
+
+ ngroup = i801_gpio_cfg[type].ngroup;
+ for (group = 0; group < ngroup; group++)
+ data->use_sel[group] = inl(data->base +
+ REG_GPIO_USE_SEL[group]);
+ i801_gpio_sel_fixup(data, id->device);
+ mutex_init(&data->lock);
+
+ i801_gpio_setup(&data->gpio, &pdev->dev, ngroup * 32);
+ i801_gpio_print_state(data, &pdev->dev, ngroup);
+
+ pci_set_drvdata(pdev, data);
+ err = gpiochip_add(&data->gpio);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to register GPIO (%d)\n", err);
+ goto err_release;
+ }
+
+ return 0;
+
+ err_release:
+ release_region(data->base, data->io_size);
+
+ err_disable:
+ pci_disable_device(pdev);
+
+ err_free:
+ kfree(data);
+ return err;
+}
+
+static void __devexit i801_gpio_remove(struct pci_dev *pdev)
+{
+ struct i801_gpio_data *data = pci_get_drvdata(pdev);
+ int err;
+
+ err = gpiochip_remove(&data->gpio);
+ if (err)
+ dev_err(&pdev->dev, "Failed to unregister GPIO (%d)\n", err);
+
+ release_region(data->base, data->io_size);
+ pci_disable_device(pdev);
+ kfree(data);
+}
+
+/* driver_data is the number of GPIO groups */
+static DEFINE_PCI_DEVICE_TABLE(i801_gpio_pcidev_id) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0),
+ .driver_data = ICH2 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0),
+ .driver_data = ICH2 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0),
+ .driver_data = ICH2 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10),
+ .driver_data = ICH2 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0),
+ .driver_data = ICH4 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12),
+ .driver_data = ICH4 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0),
+ .driver_data = ICH4 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12),
+ .driver_data = ICH4 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0),
+ .driver_data = ICH4 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_1),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_4),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_1),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_4),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_5),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_7),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_8),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_0),
+ .driver_data = ICH10_CORP },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_1),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_2),
+ .driver_data = ICH6 },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_3),
+ .driver_data = ICH10_CORP },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, i801_gpio_pcidev_id);
+
+static struct pci_driver i801_gpio_driver = {
+ .name = "i801_gpio",
+ .id_table = i801_gpio_pcidev_id,
+ .probe = i801_gpio_probe,
+ .remove = __devexit_p(i801_gpio_remove),
+};
+
+static int __init i801_gpio_pci_init(void)
+{
+ return pci_register_driver(&i801_gpio_driver);
+}
+module_init(i801_gpio_pci_init);
+
+static void __exit i801_gpio_pci_exit(void)
+{
+ pci_unregister_driver(&i801_gpio_driver);
+}
+module_exit(i801_gpio_pci_exit);
+
+MODULE_AUTHOR("Jean Delvare <[email protected]>");
+MODULE_DESCRIPTION("Intel 82801 (ICH) GPIO driver");
+MODULE_LICENSE("GPL");
--
Jean Delvare
On Tue, Apr 19, 2011 at 6:53 AM, Jean Delvare <[email protected]> wrote:
> I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> has an ICH10, I've added support for older ICH chips in case someone
> needs it, as it was relatively simply to do that.
>
> Signed-off-by: Jean Delvare <[email protected]>
> Cc: Grant Likely <[email protected]>
> ---
> Note 1: On early ICH chips, some pins are exclusively inputs or
> outputs. The driver doesn't currently enforce this.
I'm not at all concerned about this. Users *must* know what a pin is
when they start fiddling with it. If you ask a pin to do something
that it cannot do, then it just isn't going to work. There's no real
point in trying to enforce any behaviour in the API.
>
> Note 2: I'm not yet sure if we want a module alias for this driver.
> Many systems have the device but only a few of them will need the
> driver (and an ACPI resource conflict will be reported for many
> others, especially laptops I suspect.) So it might make more sense to
> let consumer drivers request the i801_gpio driver as needed (which they
> should do anyway, as you can't assume udev is always up and running on
> all systems.)
>
> Note 3: This is my first GPIO driver, so while it works fine for me, it
> might not be perfect. I welcome comments on how to improve it.
Your timing is impeccable. You're getting caught up in the big gpio
driver consolidation. :-)
Most gpio drivers end up looking pretty darn similar. Instead of
writing the same types of drivers over and over again, the new
approach is to try and consolidate the mmio drivers down to using
basic_mmio_gpio.c.
In this particular case, you've got a PCI device which looks to be
going into config space to get some information about how the chip is
layed out. What I would do is keep your existing pci probe & remove
hooks, but use them to create and register child basic_mmio_gpio
platform_devices for each gpio bank.
g.
>
> ?drivers/gpio/Kconfig ? ? | ? ?7
> ?drivers/gpio/Makefile ? ?| ? ?1
> ?drivers/gpio/i801_gpio.c | ?432 ++++++++++++++++++++++++++++++++++++++++++++++
> ?3 files changed, 440 insertions(+)
>
> --- linux-2.6.39-rc3.orig/drivers/gpio/Kconfig ?2011-04-18 19:41:51.000000000 +0200
> +++ linux-2.6.39-rc3/drivers/gpio/Kconfig ? ? ? 2011-04-18 22:36:41.000000000 +0200
> @@ -322,6 +322,13 @@ config GPIO_BT8XX
>
> ? ? ? ? ?If unsure, say N.
>
> +config GPIO_I801
> + ? ? ? tristate "Intel 82801 (ICH) GPIO"
> + ? ? ? depends on PCI
> + ? ? ? help
> + ? ? ? ? This driver is for the GPIO pins of Intel 82801 south bridges
> + ? ? ? ? (aka ICH).
> +
> ?config GPIO_LANGWELL
> ? ? ? ?bool "Intel Langwell/Penwell GPIO support"
> ? ? ? ?depends on PCI && X86
> --- linux-2.6.39-rc3.orig/drivers/gpio/Makefile 2011-04-18 19:41:51.000000000 +0200
> +++ linux-2.6.39-rc3/drivers/gpio/Makefile ? ? ?2011-04-18 22:36:41.000000000 +0200
> @@ -11,6 +11,7 @@ obj-$(CONFIG_GPIOLIB) ? ? ? ? += gpiolib.o
> ?obj-$(CONFIG_GPIO_ADP5520) ? ? += adp5520-gpio.o
> ?obj-$(CONFIG_GPIO_ADP5588) ? ? += adp5588-gpio.o
> ?obj-$(CONFIG_GPIO_BASIC_MMIO) ?+= basic_mmio_gpio.o
> +obj-$(CONFIG_GPIO_I801) ? ? ? ? ? ? ? ?+= i801_gpio.o
> ?obj-$(CONFIG_GPIO_LANGWELL) ? ?+= langwell_gpio.o
> ?obj-$(CONFIG_GPIO_MAX730X) ? ? += max730x.o
> ?obj-$(CONFIG_GPIO_MAX7300) ? ? += max7300.o
> --- /dev/null ? 1970-01-01 00:00:00.000000000 +0000
> +++ linux-2.6.39-rc3/drivers/gpio/i801_gpio.c ? 2011-04-19 13:55:25.000000000 +0200
> @@ -0,0 +1,432 @@
> +/*
> + * Linux kernel driver for the Intel 82801 GPIO pins
> + * Copyright (C) 2011 Jean Delvare <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ?See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/acpi.h>
> +#include <linux/bitops.h>
> +#include <linux/gpio.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +
> +static int force;
> +module_param(force, int, 0444);
> +MODULE_PARM_DESC(force, "Forcibly enable access to GPIO");
> +
> +enum chips { ICH2, ICH4, ICH6, ICH10_CORP };
> +
> +/* Register definitions */
> +static const u8 REG_GPIO_USE_SEL[3] ? ?= { 0x00, 0x30, 0x40 };
> +static const u8 REG_GP_IO_SEL[3] ? ? ? = { 0x04, 0x34, 0x44 };
> +static const u8 REG_GP_LVL[3] ? ? ? ? ?= { 0x0C, 0x38, 0x48 };
> +
> +/**
> + * struct i801_gpio_data - 82801 GPIO private data
> + * @base: ? ? ? ? ? ? ?Base I/O port
> + * @io_size: ? ? ? ? ? I/O region size (64 or 128)
> + * @gpio: ? ? ? ? ? ? ?Data for GPIO infrastructure
> + * @lock: ? ? ? ? ? ? ?Mutex to serialize read-modify-write cycles
> + */
> +struct i801_gpio_data {
> + ? ? ? u32 base;
> + ? ? ? u32 io_size;
> + ? ? ? u32 use_sel[3];
> + ? ? ? struct gpio_chip gpio;
> + ? ? ? struct mutex lock;
> +};
> +
> +static int i801_gpio_request(struct gpio_chip *gpio, unsigned nr)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? int group = nr >> 5;
> +
> + ? ? ? data = container_of(gpio, struct i801_gpio_data, gpio);
> + ? ? ? nr &= 0x1f;
> +
> + ? ? ? if (data->use_sel[group] & BIT(nr))
> + ? ? ? ? ? ? ? return 0;
> + ? ? ? else
> + ? ? ? ? ? ? ? return -EINVAL;
> +}
> +
> +static void i801_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? int group = nr >> 5;
> + ? ? ? u32 reg_val;
> +
> + ? ? ? data = container_of(gpio, struct i801_gpio_data, gpio);
> + ? ? ? nr &= 0x1f;
> +
> + ? ? ? mutex_lock(&data->lock);
> + ? ? ? reg_val = inl(data->base + REG_GP_LVL[group]);
> + ? ? ? if (val)
> + ? ? ? ? ? ? ? reg_val |= BIT(nr);
> + ? ? ? else
> + ? ? ? ? ? ? ? reg_val &= ~BIT(nr);
> + ? ? ? outl(reg_val, data->base + REG_GP_LVL[group]);
> + ? ? ? mutex_unlock(&data->lock);
> +}
> +
> +static int i801_gpio_get(struct gpio_chip *gpio, unsigned nr)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? int group = nr >> 5;
> +
> + ? ? ? data = container_of(gpio, struct i801_gpio_data, gpio);
> + ? ? ? nr &= 0x1f;
> +
> + ? ? ? return (inl(data->base + REG_GP_LVL[group]) >> nr) & 1;
> +}
> +
> +static int i801_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int val)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? int group = nr >> 5;
> + ? ? ? u32 reg_val;
> +
> + ? ? ? data = container_of(gpio, struct i801_gpio_data, gpio);
> + ? ? ? nr &= 0x1f;
> +
> + ? ? ? mutex_lock(&data->lock);
> + ? ? ? reg_val = inl(data->base + REG_GP_IO_SEL[group]);
> + ? ? ? reg_val &= ~BIT(nr);
> + ? ? ? outl(reg_val, data->base + REG_GP_IO_SEL[group]);
> +
> + ? ? ? reg_val = inl(data->base + REG_GP_LVL[group]);
> + ? ? ? if (val)
> + ? ? ? ? ? ? ? reg_val |= BIT(nr);
> + ? ? ? else
> + ? ? ? ? ? ? ? reg_val &= ~BIT(nr);
> + ? ? ? outl(reg_val, data->base + REG_GP_LVL[group]);
> + ? ? ? mutex_unlock(&data->lock);
> +
> + ? ? ? return 0;
> +}
> +
> +static int i801_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? int group = nr >> 5;
> + ? ? ? u32 reg_val;
> +
> + ? ? ? data = container_of(gpio, struct i801_gpio_data, gpio);
> + ? ? ? nr &= 0x1f;
> +
> + ? ? ? mutex_lock(&data->lock);
> + ? ? ? reg_val = inl(data->base + REG_GP_IO_SEL[group]);
> + ? ? ? reg_val |= BIT(nr);
> + ? ? ? outl(reg_val, data->base + REG_GP_IO_SEL[group]);
> + ? ? ? mutex_unlock(&data->lock);
> +
> + ? ? ? return 0;
> +}
> +
> +static void __devinit i801_gpio_setup(struct gpio_chip *gpio,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct device *dev, int ngpio)
> +{
> + ? ? ? gpio->label = "i801_gpio";
> + ? ? ? gpio->dev = dev;
> + ? ? ? gpio->owner = THIS_MODULE;
> + ? ? ? gpio->request = i801_gpio_request;
> + ? ? ? gpio->direction_input = i801_gpio_direction_input;
> + ? ? ? gpio->get = i801_gpio_get;
> + ? ? ? gpio->direction_output = i801_gpio_direction_output;
> + ? ? ? gpio->set = i801_gpio_set;
> + ? ? ? gpio->base = -1;
> + ? ? ? gpio->ngpio = ngpio;
> +}
> +
> +/*
> + * On the ICH/ICH0, ICH3, ICH4 and ICH5, some selection bits control more
> + * than one GPIO.
> + */
> +static void __devinit i801_gpio_sel_fixup(struct i801_gpio_data *data,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? u32 device)
> +{
> + ? ? ? switch (device) {
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801AA_0:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801AB_0:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801CA_0:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801CA_12:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801DB_0:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801DB_12:
> + ? ? ? case PCI_DEVICE_ID_INTEL_82801EB_0:
> + ? ? ? ? ? ? ? if (data->use_sel[0] & BIT(0))
> + ? ? ? ? ? ? ? ? ? ? ? data->use_sel[0] |= BIT(16);
> + ? ? ? ? ? ? ? if (data->use_sel[0] & BIT(1))
> + ? ? ? ? ? ? ? ? ? ? ? data->use_sel[0] |= BIT(17);
> + ? ? ? ? ? ? ? if (data->use_sel[0] & BIT(27))
> + ? ? ? ? ? ? ? ? ? ? ? data->use_sel[0] |= BIT(28);
> + ? ? ? ? ? ? ? break;
> + ? ? ? }
> +}
> +
> +static __devinitdata
> +const struct {
> + ? ? ? int ngroup;
> + ? ? ? int io_size;
> + ? ? ? u8 reg_gpiobase;
> + ? ? ? u8 reg_gc;
> +} i801_gpio_cfg[] = {
> + ? ? ? [ICH2] ? ? ? ? ?= { 1, ?64, 0x58, 0x5C },
> + ? ? ? [ICH4] ? ? ? ? ?= { 2, ?64, 0x58, 0x5C },
> + ? ? ? [ICH6] ? ? ? ? ?= { 2, ?64, 0x48, 0x4C },
> + ? ? ? [ICH10_CORP] ? ?= { 3, 128, 0x48, 0x4C },
> +};
> +
> +static void __devinit i801_gpio_print_state(struct i801_gpio_data *data,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct device *dev, int ngroup)
> +{
> + ? ? ? int i, group;
> + ? ? ? u32 io_sel, lvl;
> +
> + ? ? ? dev_dbg(dev, "Current state of GPIO pins:\n");
> + ? ? ? for (group = 0; group < ngroup; group++) {
> + ? ? ? ? ? ? ? io_sel = inl(data->base + REG_GP_IO_SEL[group]);
> + ? ? ? ? ? ? ? lvl = inl(data->base + REG_GP_LVL[group]);
> +
> + ? ? ? ? ? ? ? for (i = 0; i < 32; i++) {
> + ? ? ? ? ? ? ? ? ? ? ? if (!(data->use_sel[group] & BIT(i)))
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? continue;
> +
> + ? ? ? ? ? ? ? ? ? ? ? dev_dbg(dev, "GPIO%d: %s, level %d\n", group * 32 + i,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? io_sel & BIT(i) ? "input" : "output",
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? !!(lvl & BIT(i)));
> + ? ? ? ? ? ? ? }
> + ? ? ? }
> +}
> +
> +static int __devinit i801_gpio_probe(struct pci_dev *pdev,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const struct pci_device_id *id)
> +{
> + ? ? ? struct i801_gpio_data *data;
> + ? ? ? u32 gpiobase;
> + ? ? ? u8 gc;
> + ? ? ? int type = id->driver_data;
> + ? ? ? int group, ngroup, err;
> +
> + ? ? ? data = kzalloc(sizeof(struct i801_gpio_data), GFP_KERNEL);
> + ? ? ? if (!data)
> + ? ? ? ? ? ? ? return -ENOMEM;
> +
> + ? ? ? err = pci_enable_device(pdev);
> + ? ? ? if (err) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Failed to enable device (%d)\n", err);
> + ? ? ? ? ? ? ? goto err_free;
> + ? ? ? }
> +
> + ? ? ? /* Get the base I/O address */
> + ? ? ? err = pci_read_config_dword(pdev, i801_gpio_cfg[type].reg_gpiobase,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &gpiobase);
> + ? ? ? if (err) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
> + ? ? ? ? ? ? ? ? ? ? ? "GPIOBASE", err);
> + ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> + ? ? ? data->base = gpiobase & ~BIT(0);
> + ? ? ? if (!data->base) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "GPIOBASE not set\n");
> + ? ? ? ? ? ? ? err = -ENODEV;
> + ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> +
> + ? ? ? /* Check configuration */
> + ? ? ? err = pci_read_config_byte(pdev, i801_gpio_cfg[type].reg_gc, &gc);
> + ? ? ? if (err) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
> + ? ? ? ? ? ? ? ? ? ? ? "GC", err);
> + ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> + ? ? ? if (gc & BIT(0)) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "GPIO function is %s\n", "locked");
> + ? ? ? ? ? ? ? err = -EBUSY;
> + ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> + ? ? ? if (!(gc & BIT(4))) {
> + ? ? ? ? ? ? ? if (!force) {
> + ? ? ? ? ? ? ? ? ? ? ? dev_err(&pdev->dev, "GPIO function is %s\n",
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "disabled");
> + ? ? ? ? ? ? ? ? ? ? ? err = -ENODEV;
> + ? ? ? ? ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? ? ? ? ? }
> +
> + ? ? ? ? ? ? ? gc |= BIT(4);
> + ? ? ? ? ? ? ? err = pci_write_config_byte(pdev,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? i801_gpio_cfg[type].reg_gc, gc);
> + ? ? ? ? ? ? ? if (err) {
> + ? ? ? ? ? ? ? ? ? ? ? dev_err(&pdev->dev,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "Failed to enable GPIO function (%d)\n",
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? err);
> + ? ? ? ? ? ? ? ? ? ? ? err = -ENODEV;
> + ? ? ? ? ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? ? ? ? ? }
> + ? ? ? ? ? ? ? dev_info(&pdev->dev, "Enabling GPIO function\n");
> + ? ? ? }
> +
> + ? ? ? /*
> + ? ? ? ?* "Corporate" incarnations of the ICH10 have an I/O region of size
> + ? ? ? ?* 128 and 73 GPIO pins. Others ("consumer") have an I/O region of
> + ? ? ? ?* size only 64 and 61 GPIO pins, as the ICH6, ICH7, ICH8 and ICH9
> + ? ? ? ?* had.
> + ? ? ? ?*/
> + ? ? ? data->io_size = i801_gpio_cfg[type].io_size;
> + ? ? ? if (!force) {
> + ? ? ? ? ? ? ? err = acpi_check_region(data->base, data->io_size, "i801_gpio");
> + ? ? ? ? ? ? ? if (err)
> + ? ? ? ? ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> + ? ? ? if (!request_region(data->base, data->io_size, "i801_gpio")) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Failed to request I/O ports (%d)", err);
> + ? ? ? ? ? ? ? err = -EBUSY;
> + ? ? ? ? ? ? ? goto err_disable;
> + ? ? ? }
> +
> + ? ? ? ngroup = i801_gpio_cfg[type].ngroup;
> + ? ? ? for (group = 0; group < ngroup; group++)
> + ? ? ? ? ? ? ? data->use_sel[group] = inl(data->base +
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?REG_GPIO_USE_SEL[group]);
> + ? ? ? i801_gpio_sel_fixup(data, id->device);
> + ? ? ? mutex_init(&data->lock);
> +
> + ? ? ? i801_gpio_setup(&data->gpio, &pdev->dev, ngroup * 32);
> + ? ? ? i801_gpio_print_state(data, &pdev->dev, ngroup);
> +
> + ? ? ? pci_set_drvdata(pdev, data);
> + ? ? ? err = gpiochip_add(&data->gpio);
> + ? ? ? if (err) {
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Failed to register GPIO (%d)\n", err);
> + ? ? ? ? ? ? ? goto err_release;
> + ? ? ? }
> +
> + ? ? ? return 0;
> +
> + err_release:
> + ? ? ? release_region(data->base, data->io_size);
> +
> + err_disable:
> + ? ? ? pci_disable_device(pdev);
> +
> + err_free:
> + ? ? ? kfree(data);
> + ? ? ? return err;
> +}
> +
> +static void __devexit i801_gpio_remove(struct pci_dev *pdev)
> +{
> + ? ? ? struct i801_gpio_data *data = pci_get_drvdata(pdev);
> + ? ? ? int err;
> +
> + ? ? ? err = gpiochip_remove(&data->gpio);
> + ? ? ? if (err)
> + ? ? ? ? ? ? ? dev_err(&pdev->dev, "Failed to unregister GPIO (%d)\n", err);
> +
> + ? ? ? release_region(data->base, data->io_size);
> + ? ? ? pci_disable_device(pdev);
> + ? ? ? kfree(data);
> +}
> +
> +/* driver_data is the number of GPIO groups */
> +static DEFINE_PCI_DEVICE_TABLE(i801_gpio_pcidev_id) = {
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0),
> + ? ? ? ? .driver_data = ICH2 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0),
> + ? ? ? ? .driver_data = ICH2 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0),
> + ? ? ? ? .driver_data = ICH2 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10),
> + ? ? ? ? .driver_data = ICH2 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0),
> + ? ? ? ? .driver_data = ICH4 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12),
> + ? ? ? ? .driver_data = ICH4 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0),
> + ? ? ? ? .driver_data = ICH4 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12),
> + ? ? ? ? .driver_data = ICH4 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0),
> + ? ? ? ? .driver_data = ICH4 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_1),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_4),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_1),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_4),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_5),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_7),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_8),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_0),
> + ? ? ? ? .driver_data = ICH10_CORP },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_1),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_2),
> + ? ? ? ? .driver_data = ICH6 },
> + ? ? ? { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_3),
> + ? ? ? ? .driver_data = ICH10_CORP },
> + ? ? ? { 0, }
> +};
> +MODULE_DEVICE_TABLE(pci, i801_gpio_pcidev_id);
> +
> +static struct pci_driver i801_gpio_driver = {
> + ? ? ? .name ? ? ? ? ? = "i801_gpio",
> + ? ? ? .id_table ? ? ? = i801_gpio_pcidev_id,
> + ? ? ? .probe ? ? ? ? ?= i801_gpio_probe,
> + ? ? ? .remove ? ? ? ? = __devexit_p(i801_gpio_remove),
> +};
> +
> +static int __init i801_gpio_pci_init(void)
> +{
> + ? ? ? return pci_register_driver(&i801_gpio_driver);
> +}
> +module_init(i801_gpio_pci_init);
> +
> +static void __exit i801_gpio_pci_exit(void)
> +{
> + ? ? ? pci_unregister_driver(&i801_gpio_driver);
> +}
> +module_exit(i801_gpio_pci_exit);
> +
> +MODULE_AUTHOR("Jean Delvare <[email protected]>");
> +MODULE_DESCRIPTION("Intel 82801 (ICH) GPIO driver");
> +MODULE_LICENSE("GPL");
>
>
> --
> Jean Delvare
>
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
> I'm not at all concerned about this. Users *must* know what a pin is
> when they start fiddling with it. If you ask a pin to do something
> that it cannot do, then it just isn't going to work. There's no real
> point in trying to enforce any behaviour in the API.
Disagree 8) - errors are better than silently and undetectably failing.
That might not be an error code but ideally it would WARN() or
similar so that errors get caught and fixed and you don't instead find
out something was poking the wrong pin and worked by chance a year later.
> In this particular case, you've got a PCI device which looks to be
> going into config space to get some information about how the chip is
> layed out. What I would do is keep your existing pci probe & remove
> hooks, but use them to create and register child basic_mmio_gpio
> platform_devices for each gpio bank.
That means more devices, weird sysfs heirarchies and lots of mess with
runtime power management in the general case.
If the basic_mmio_gpio code is properly abstracted it ought not to need
magic extra platform devices.
That aside it'll be less code to keep it separate than do all the messing
around with platform devices so it seems to be a gross overdoing of 'lets
make everything use the same code however big a hammer is needed'
If it was a platform device it might make sense, if the config was
platform config to create a device it would certainly make sense, in the
PCI case it's smaller, cleaner and saner not to add insane layers of glue
and indirection IMHO.
Alan
On Tue, Apr 19, 2011 at 8:54 AM, Alan Cox <[email protected]> wrote:
>> In this particular case, you've got a PCI device which looks to be
>> going into config space to get some information about how the chip is
>> layed out. ?What I would do is keep your existing pci probe & remove
>> hooks, but use them to create and register child basic_mmio_gpio
>> platform_devices for each gpio bank.
>
> That means more devices, weird sysfs heirarchies and lots of mess with
> runtime power management in the general case.
>
> If the basic_mmio_gpio code is properly abstracted it ought not to need
> magic extra platform devices.
>
> That aside it'll be less code to keep it separate than do all the messing
> around with platform devices so it seems to be a gross overdoing of 'lets
> make everything use the same code however big a hammer is needed'
>
> If it was a platform device it might make sense, if the config was
> platform config to create a device it would certainly make sense, in the
> PCI case it's smaller, cleaner and saner not to add insane layers of glue
> and indirection IMHO.
Doing a platform device is one solution that is pretty simple to
implement and I don't think adding child devices is at all a problem.
However, I'm not mandating that approach, and if you or Jean prefer to
abstract out the gpio accessors from basic_mmio_gpio(), then that is
fine by me. The key issue it to avoid merging yet another, probably
subtly broken, set of GPIO accessor functions if it can at all be
avoided.
g.
> Doing a platform device is one solution that is pretty simple to
> implement and I don't think adding child devices is at all a problem.
> However, I'm not mandating that approach, and if you or Jean prefer to
> abstract out the gpio accessors from basic_mmio_gpio(), then that is
> fine by me. The key issue it to avoid merging yet another, probably
> subtly broken, set of GPIO accessor functions if it can at all be
> avoided.
As is adding a set of subtly broken attempts to create device stacks that
then get into funny sysfs and power management tangles. As well as being
about 12K larger due to the need to suck in an extra module.
I can see the point of splitting out the accessors but if thats a module
of its own then thats another 8K we don't need to waste on a few hundred
bytes of utterly trivial code.
On Tue, Apr 19, 2011 at 04:57:46PM +0100, Alan Cox wrote:
> > Doing a platform device is one solution that is pretty simple to
> > implement and I don't think adding child devices is at all a problem.
> > However, I'm not mandating that approach, and if you or Jean prefer to
> > abstract out the gpio accessors from basic_mmio_gpio(), then that is
> > fine by me. The key issue it to avoid merging yet another, probably
> > subtly broken, set of GPIO accessor functions if it can at all be
> > avoided.
>
> As is adding a set of subtly broken attempts to create device stacks that
> then get into funny sysfs and power management tangles. As well as being
> about 12K larger due to the need to suck in an extra module.
>
> I can see the point of splitting out the accessors but if thats a module
> of its own then thats another 8K we don't need to waste on a few hundred
> bytes of utterly trivial code.
How about exporting some bus/device-type neutral probe function, like
we do in drivers/ata/pata_platform.c ?
That way bus-specific probe functions may call the generic routines.
And if you still want to save 8K, you could place the PCI bindings
inside the basic_mmio_gpio.
The patch below is against Linus tree + the following patches from
Jamie Iles:
basic_mmio_gpio: remove runtime width/endianness evaluation
basic_mmio_gpio: convert to platform_{get,set}_drvdata()
basic_mmio_gpio: allow overriding number of gpio
basic_mmio_gpio: request register regions
basic_mmio_gpio: detect output method at probe time
basic_mmio_gpio: support different input/output registers
basic_mmio_gpio: support direction registers
basic_mmio_gpio: convert to non-__raw* accessors
Not-yet-signed-off-by: Anton Vorontsov <[email protected]>
and only compile tested so far, just an idea:
---
drivers/gpio/Kconfig | 6 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/basic_mmio_gpio.c | 98 ++++++++++++++++++++++----------------
include/linux/basic_mmio_gpio.h | 14 ++++++
4 files changed, 78 insertions(+), 41 deletions(-)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 664660e..88dd650 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -70,8 +70,14 @@ config GPIO_MAX730X
comment "Memory mapped GPIO expanders:"
+config GPIO_BASIC_MMIO_CORE
+ tristate
+ help
+ Provides core functionality for basic memory-mapped GPIO controllers.
+
config GPIO_BASIC_MMIO
tristate "Basic memory-mapped GPIO controllers support"
+ select GPIO_BASIC_MMIO_CORE
help
Say yes here to support basic memory-mapped GPIO controllers.
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 3351cf8..1abf916 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o
obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o
obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o
+obj-$(CONFIG_GPIO_BASIC_MMIO_CORE) += basic_mmio_gpio.o
obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o
obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o
obj-$(CONFIG_GPIO_MAX730X) += max730x.o
diff --git a/drivers/gpio/basic_mmio_gpio.c b/drivers/gpio/basic_mmio_gpio.c
index b2ec45f..3deb1d7 100644
--- a/drivers/gpio/basic_mmio_gpio.c
+++ b/drivers/gpio/basic_mmio_gpio.c
@@ -294,10 +294,10 @@ static void __iomem *bgpio_request_and_map(struct device *dev,
return devm_ioremap(dev, res->start, resource_size(res));
}
-static int bgpio_setup_accessors(struct platform_device *pdev,
- struct bgpio_chip *bgc)
+static int bgpio_setup_accessors(struct device *dev,
+ struct bgpio_chip *bgc,
+ bool be)
{
- const struct platform_device_id *platid = platform_get_device_id(pdev);
switch (bgc->bits) {
case 8:
@@ -319,13 +319,11 @@ static int bgpio_setup_accessors(struct platform_device *pdev,
break;
#endif /* BITS_PER_LONG >= 64 */
default:
- dev_err(&pdev->dev, "unsupported data width %u bits\n",
- bgc->bits);
+ dev_err(dev, "unsupported data width %u bits\n", bgc->bits);
return -EINVAL;
}
- bgc->pin2mask = strcmp(platid->name, "basic-mmio-gpio-be") ?
- bgpio_pin2mask : bgpio_pin2mask_be;
+ bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask;
return 0;
}
@@ -352,18 +350,14 @@ static int bgpio_setup_accessors(struct platform_device *pdev,
* - an input direction register (named "dirin") where a 1 bit indicates
* the GPIO is an input.
*/
-static int bgpio_setup_io(struct platform_device *pdev,
- struct bgpio_chip *bgc)
+static int bgpio_setup_io(struct device *dev,
+ struct bgpio_chip *bgc,
+ struct resource *res_dat,
+ struct resource *res_set,
+ struct resource *res_clr)
{
- struct resource *res_set;
- struct resource *res_clr;
- struct resource *res_dat;
resource_size_t dat_sz;
- res_dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
- if (!res_dat)
- return -EINVAL;
-
dat_sz = resource_size(res_dat);
if (!is_power_of_2(dat_sz))
return -EINVAL;
@@ -372,19 +366,17 @@ static int bgpio_setup_io(struct platform_device *pdev,
if (bgc->bits > BITS_PER_LONG)
return -EINVAL;
- bgc->reg_dat = bgpio_request_and_map(&pdev->dev, res_dat);
+ bgc->reg_dat = bgpio_request_and_map(dev, res_dat);
if (!bgc->reg_dat)
return -ENOMEM;
- res_set = platform_get_resource_byname(pdev, IORESOURCE_MEM, "set");
- res_clr = platform_get_resource_byname(pdev, IORESOURCE_MEM, "clr");
if (res_set && res_clr) {
if (resource_size(res_set) != resource_size(res_clr) ||
resource_size(res_set) != resource_size(res_dat))
return -EINVAL;
- bgc->reg_set = bgpio_request_and_map(&pdev->dev, res_set);
- bgc->reg_clr = bgpio_request_and_map(&pdev->dev, res_clr);
+ bgc->reg_set = bgpio_request_and_map(dev, res_set);
+ bgc->reg_clr = bgpio_request_and_map(dev, res_clr);
if (!bgc->reg_set || !bgc->reg_clr)
return -ENOMEM;
@@ -393,7 +385,7 @@ static int bgpio_setup_io(struct platform_device *pdev,
if (resource_size(res_set) != resource_size(res_dat))
return -EINVAL;
- bgc->reg_set = bgpio_request_and_map(&pdev->dev, res_set);
+ bgc->reg_set = bgpio_request_and_map(dev, res_set);
if (!bgc->reg_set)
return -ENOMEM;
@@ -407,25 +399,21 @@ static int bgpio_setup_io(struct platform_device *pdev,
return 0;
}
-static int bgpio_setup_direction(struct platform_device *pdev,
- struct bgpio_chip *bgc)
+static int bgpio_setup_direction(struct device *dev,
+ struct bgpio_chip *bgc,
+ struct resource *res_dirout,
+ struct resource *res_dirin)
{
- struct resource *res_dirout;
- struct resource *res_dirin;
-
- res_dirout = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "dirout");
- res_dirin = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirin");
if (res_dirout && res_dirin) {
return -EINVAL;
} else if (res_dirout) {
- bgc->reg_dir = bgpio_request_and_map(&pdev->dev, res_dirout);
+ bgc->reg_dir = bgpio_request_and_map(dev, res_dirout);
if (!bgc->reg_dir)
return -ENOMEM;
bgc->gc.direction_output = bgpio_dir_out;
bgc->gc.direction_input = bgpio_dir_in;
} else if (res_dirin) {
- bgc->reg_dir = bgpio_request_and_map(&pdev->dev, res_dirin);
+ bgc->reg_dir = bgpio_request_and_map(dev, res_dirin);
if (!bgc->reg_dir)
return -ENOMEM;
bgc->gc.direction_output = bgpio_dir_out_inv;
@@ -438,9 +426,22 @@ static int bgpio_setup_direction(struct platform_device *pdev,
return 0;
}
-static int __devinit bgpio_probe(struct platform_device *pdev)
+int __devexit __bgpio_remove(struct device *dev)
+{
+ struct bgpio_chip *bgc = dev_get_drvdata(dev);
+
+ return gpiochip_remove(&bgc->gc);
+}
+EXPORT_SYMBOL_GPL(__bgpio_remove);
+
+int __devinit __bgpio_probe(struct device *dev,
+ struct resource *res_dat,
+ struct resource *res_set,
+ struct resource *res_clr,
+ struct resource *res_dirout,
+ struct resource *res_dirin,
+ bool be)
{
- struct device *dev = &pdev->dev;
struct bgpio_pdata *pdata = dev_get_platdata(dev);
struct bgpio_chip *bgc;
int ret;
@@ -450,7 +451,7 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
if (!bgc)
return -ENOMEM;
- ret = bgpio_setup_io(pdev, bgc);
+ ret = bgpio_setup_io(dev, bgc, res_dat, res_set, res_clr);
if (ret)
return ret;
@@ -463,12 +464,12 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
bgc->gc.base = -1;
}
- ret = bgpio_setup_accessors(pdev, bgc);
+ ret = bgpio_setup_accessors(dev, bgc, be);
if (ret)
return ret;
spin_lock_init(&bgc->lock);
- ret = bgpio_setup_direction(pdev, bgc);
+ ret = bgpio_setup_direction(dev, bgc, res_dirout, res_dirin);
if (ret)
return ret;
@@ -478,7 +479,7 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
bgc->gc.dev = dev;
bgc->gc.label = dev_name(dev);
- platform_set_drvdata(pdev, bgc);
+ dev_set_drvdata(dev, bgc);
ret = gpiochip_add(&bgc->gc);
if (ret)
@@ -486,12 +487,25 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
return ret;
}
+EXPORT_SYMBOL_GPL(__bgpio_probe);
-static int __devexit bgpio_remove(struct platform_device *pdev)
+#ifdef CONFIG_GPIO_BASIC_MMIO
+
+static int __devinit bgpio_probe(struct platform_device *pdev)
{
- struct bgpio_chip *bgc = platform_get_drvdata(pdev);
+ return __bgpio_probe(&pdev->dev,
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat"),
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "set"),
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "clr"),
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirout"),
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirin"),
+ !strcmp(platform_get_device_id(pdev)->name,
+ "basic-mmio-gpio-be"));
+}
- return gpiochip_remove(&bgc->gc);
+static int __devexit bgpio_remove(struct platform_device *pdev)
+{
+ return __bgpio_remove(&pdev->dev);
}
static const struct platform_device_id bgpio_id_table[] = {
@@ -522,6 +536,8 @@ static void __exit bgpio_exit(void)
}
module_exit(bgpio_exit);
+#endif /* CONFIG_GPIO_BASIC_MMIO */
+
MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers");
MODULE_AUTHOR("Anton Vorontsov <[email protected]>");
MODULE_LICENSE("GPL");
diff --git a/include/linux/basic_mmio_gpio.h b/include/linux/basic_mmio_gpio.h
index f23ec73..9277e59 100644
--- a/include/linux/basic_mmio_gpio.h
+++ b/include/linux/basic_mmio_gpio.h
@@ -13,9 +13,23 @@
#ifndef __BASIC_MMIO_GPIO_H
#define __BASIC_MMIO_GPIO_H
+#include <linux/types.h>
+
struct bgpio_pdata {
int base;
int ngpio;
};
+struct device;
+struct resource;
+
+int __devexit __bgpio_remove(struct device *dev);
+int __devinit __bgpio_probe(struct device *dev,
+ struct resource *res_dat,
+ struct resource *res_set,
+ struct resource *res_clr,
+ struct resource *res_dirout,
+ struct resource *res_dirin,
+ bool be);
+
#endif /* __BASIC_MMIO_GPIO_H */
--
1.7.4.1
> How about exporting some bus/device-type neutral probe function, like
> we do in drivers/ata/pata_platform.c ?
Not sure you want to be using resources as the basic currency, nor do you
want your generic code doing the request_region stuff. That was a nasty
mistake we made in the IDE code.
Maybe the platform code should do that bit, but the generic stuff not - in
the PCI case the caller will have done pci_request_* itself and chances
are several of the resources are the same pci device resource and
different offsets so doing request_resource on them will blow up.
Concept looks good
Alan
On Tue, Apr 19, 2011 at 06:08:29PM +0100, Alan Cox wrote:
> > How about exporting some bus/device-type neutral probe function, like
> > we do in drivers/ata/pata_platform.c ?
>
> Not sure you want to be using resources as the basic currency, nor do you
> want your generic code doing the request_region stuff. That was a nasty
> mistake we made in the IDE code.
>
> Maybe the platform code should do that bit, but the generic stuff not - in
> the PCI case the caller will have done pci_request_* itself and chances
> are several of the resources are the same pci device resource and
> different offsets so doing request_resource on them will blow up.
Oh, right. How about this:
int __devinit bgpio_probe(struct device *dev,
unsigned long sz,
void __iomem *dat,
void __iomem *set,
void __iomem *clr,
void __iomem *dirout,
void __iomem *dirin,
bool big_endian);
I.e. PCI driver can now map the whole area with a single ioremap(),
and then call bgpio_probe(), which will provide accessors and
common MMIO GPIO registration stuff.
Not-yet-signed-off-by: Anton Vorontsov <[email protected]>
---
drivers/gpio/Kconfig | 6 +
drivers/gpio/Makefile | 1 +
drivers/gpio/basic_mmio_gpio.c | 241 ++++++++++++++++++++++++---------------
include/linux/basic_mmio_gpio.h | 15 +++
4 files changed, 171 insertions(+), 92 deletions(-)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 664660e..88dd650 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -70,8 +70,14 @@ config GPIO_MAX730X
comment "Memory mapped GPIO expanders:"
+config GPIO_BASIC_MMIO_CORE
+ tristate
+ help
+ Provides core functionality for basic memory-mapped GPIO controllers.
+
config GPIO_BASIC_MMIO
tristate "Basic memory-mapped GPIO controllers support"
+ select GPIO_BASIC_MMIO_CORE
help
Say yes here to support basic memory-mapped GPIO controllers.
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 3351cf8..1abf916 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o
obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o
obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o
+obj-$(CONFIG_GPIO_BASIC_MMIO_CORE) += basic_mmio_gpio.o
obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o
obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o
obj-$(CONFIG_GPIO_MAX730X) += max730x.o
diff --git a/drivers/gpio/basic_mmio_gpio.c b/drivers/gpio/basic_mmio_gpio.c
index b2ec45f..947734a 100644
--- a/drivers/gpio/basic_mmio_gpio.c
+++ b/drivers/gpio/basic_mmio_gpio.c
@@ -284,20 +284,10 @@ static int bgpio_dir_out_inv(struct gpio_chip *gc, unsigned int gpio, int val)
return 0;
}
-static void __iomem *bgpio_request_and_map(struct device *dev,
- struct resource *res)
+static int bgpio_setup_accessors(struct device *dev,
+ struct bgpio_chip *bgc,
+ bool be)
{
- if (!devm_request_mem_region(dev, res->start, resource_size(res),
- res->name ?: "mmio_gpio"))
- return NULL;
-
- return devm_ioremap(dev, res->start, resource_size(res));
-}
-
-static int bgpio_setup_accessors(struct platform_device *pdev,
- struct bgpio_chip *bgc)
-{
- const struct platform_device_id *platid = platform_get_device_id(pdev);
switch (bgc->bits) {
case 8:
@@ -319,13 +309,11 @@ static int bgpio_setup_accessors(struct platform_device *pdev,
break;
#endif /* BITS_PER_LONG >= 64 */
default:
- dev_err(&pdev->dev, "unsupported data width %u bits\n",
- bgc->bits);
+ dev_err(dev, "unsupported data width %u bits\n", bgc->bits);
return -EINVAL;
}
- bgc->pin2mask = strcmp(platid->name, "basic-mmio-gpio-be") ?
- bgpio_pin2mask : bgpio_pin2mask_be;
+ bgc->pin2mask = be ? bgpio_pin2mask_be : bgpio_pin2mask;
return 0;
}
@@ -352,51 +340,22 @@ static int bgpio_setup_accessors(struct platform_device *pdev,
* - an input direction register (named "dirin") where a 1 bit indicates
* the GPIO is an input.
*/
-static int bgpio_setup_io(struct platform_device *pdev,
- struct bgpio_chip *bgc)
+static int bgpio_setup_io(struct bgpio_chip *bgc,
+ void __iomem *dat,
+ void __iomem *set,
+ void __iomem *clr)
{
- struct resource *res_set;
- struct resource *res_clr;
- struct resource *res_dat;
- resource_size_t dat_sz;
-
- res_dat = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
- if (!res_dat)
- return -EINVAL;
-
- dat_sz = resource_size(res_dat);
- if (!is_power_of_2(dat_sz))
- return -EINVAL;
-
- bgc->bits = dat_sz * 8;
- if (bgc->bits > BITS_PER_LONG)
- return -EINVAL;
- bgc->reg_dat = bgpio_request_and_map(&pdev->dev, res_dat);
+ bgc->reg_dat = dat;
if (!bgc->reg_dat)
- return -ENOMEM;
-
- res_set = platform_get_resource_byname(pdev, IORESOURCE_MEM, "set");
- res_clr = platform_get_resource_byname(pdev, IORESOURCE_MEM, "clr");
- if (res_set && res_clr) {
- if (resource_size(res_set) != resource_size(res_clr) ||
- resource_size(res_set) != resource_size(res_dat))
- return -EINVAL;
-
- bgc->reg_set = bgpio_request_and_map(&pdev->dev, res_set);
- bgc->reg_clr = bgpio_request_and_map(&pdev->dev, res_clr);
- if (!bgc->reg_set || !bgc->reg_clr)
- return -ENOMEM;
+ return -EINVAL;
+ if (set && clr) {
+ bgc->reg_set = set;
+ bgc->reg_clr = clr;
bgc->gc.set = bgpio_set_with_clear;
- } else if (res_set && !res_clr) {
- if (resource_size(res_set) != resource_size(res_dat))
- return -EINVAL;
-
- bgc->reg_set = bgpio_request_and_map(&pdev->dev, res_set);
- if (!bgc->reg_set)
- return -ENOMEM;
-
+ } else if (set && !clr) {
+ bgc->reg_set = set;
bgc->gc.set = bgpio_set_set;
} else {
bgc->gc.set = bgpio_set;
@@ -407,27 +366,18 @@ static int bgpio_setup_io(struct platform_device *pdev,
return 0;
}
-static int bgpio_setup_direction(struct platform_device *pdev,
- struct bgpio_chip *bgc)
+static int bgpio_setup_direction(struct bgpio_chip *bgc,
+ void __iomem *dirout,
+ void __iomem *dirin)
{
- struct resource *res_dirout;
- struct resource *res_dirin;
-
- res_dirout = platform_get_resource_byname(pdev, IORESOURCE_MEM,
- "dirout");
- res_dirin = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirin");
- if (res_dirout && res_dirin) {
+ if (dirout && dirin) {
return -EINVAL;
- } else if (res_dirout) {
- bgc->reg_dir = bgpio_request_and_map(&pdev->dev, res_dirout);
- if (!bgc->reg_dir)
- return -ENOMEM;
+ } else if (dirout) {
+ bgc->reg_dir = dirout;
bgc->gc.direction_output = bgpio_dir_out;
bgc->gc.direction_input = bgpio_dir_in;
- } else if (res_dirin) {
- bgc->reg_dir = bgpio_request_and_map(&pdev->dev, res_dirin);
- if (!bgc->reg_dir)
- return -ENOMEM;
+ } else if (dirin) {
+ bgc->reg_dir = dirin;
bgc->gc.direction_output = bgpio_dir_out_inv;
bgc->gc.direction_input = bgpio_dir_in_inv;
} else {
@@ -438,9 +388,23 @@ static int bgpio_setup_direction(struct platform_device *pdev,
return 0;
}
-static int __devinit bgpio_probe(struct platform_device *pdev)
+int __devexit bgpio_remove(struct device *dev)
+{
+ struct bgpio_chip *bgc = dev_get_drvdata(dev);
+
+ return gpiochip_remove(&bgc->gc);
+}
+EXPORT_SYMBOL_GPL(bgpio_remove);
+
+int __devinit bgpio_probe(struct device *dev,
+ unsigned long sz,
+ void __iomem *dat,
+ void __iomem *set,
+ void __iomem *clr,
+ void __iomem *dirout,
+ void __iomem *dirin,
+ bool big_endian)
{
- struct device *dev = &pdev->dev;
struct bgpio_pdata *pdata = dev_get_platdata(dev);
struct bgpio_chip *bgc;
int ret;
@@ -450,9 +414,16 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
if (!bgc)
return -ENOMEM;
- ret = bgpio_setup_io(pdev, bgc);
- if (ret)
- return ret;
+ if (!is_power_of_2(sz))
+ return -EINVAL;
+
+ bgc->bits = sz * 8;
+ if (bgc->bits > BITS_PER_LONG)
+ return -EINVAL;
+
+ spin_lock_init(&bgc->lock);
+ bgc->gc.dev = dev;
+ bgc->gc.label = dev_name(dev);
ngpio = bgc->bits;
if (pdata) {
@@ -463,22 +434,23 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
bgc->gc.base = -1;
}
- ret = bgpio_setup_accessors(pdev, bgc);
+ bgc->gc.ngpio = ngpio;
+
+ ret = bgpio_setup_io(bgc, dat, set, clr);
if (ret)
return ret;
- spin_lock_init(&bgc->lock);
- ret = bgpio_setup_direction(pdev, bgc);
+ ret = bgpio_setup_accessors(dev, bgc, big_endian);
if (ret)
return ret;
- bgc->data = bgc->read_reg(bgc->reg_dat);
+ ret = bgpio_setup_direction(bgc, dirout, dirin);
+ if (ret)
+ return ret;
- bgc->gc.ngpio = ngpio;
- bgc->gc.dev = dev;
- bgc->gc.label = dev_name(dev);
+ bgc->data = bgc->read_reg(bgc->reg_dat);
- platform_set_drvdata(pdev, bgc);
+ dev_set_drvdata(dev, bgc);
ret = gpiochip_add(&bgc->gc);
if (ret)
@@ -486,12 +458,95 @@ static int __devinit bgpio_probe(struct platform_device *pdev)
return ret;
}
+EXPORT_SYMBOL_GPL(bgpio_probe);
-static int __devexit bgpio_remove(struct platform_device *pdev)
+#ifdef CONFIG_GPIO_BASIC_MMIO
+
+static void __iomem *bgpio_map(struct platform_device *pdev,
+ const char *name,
+ resource_size_t sane_sz,
+ int *err)
{
- struct bgpio_chip *bgc = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ resource_size_t start;
+ resource_size_t sz;
+ void __iomem *ret;
- return gpiochip_remove(&bgc->gc);
+ *err = 0;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ if (!r)
+ return NULL;
+
+ sz = resource_size(r);
+ if (sz != sane_sz) {
+ *err = -EINVAL;
+ return NULL;
+ }
+
+ start = r->start;
+ if (!devm_request_mem_region(dev, start, sz, r->name)) {
+ *err = -EBUSY;
+ return NULL;
+ }
+
+ ret = devm_ioremap(dev, start, sz);
+ if (!ret) {
+ *err = -ENOMEM;
+ return NULL;
+ }
+
+ return ret;
+}
+
+static int __devinit bgpio_pdev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ void __iomem *dat;
+ void __iomem *set;
+ void __iomem *clr;
+ void __iomem *dirout;
+ void __iomem *dirin;
+ unsigned long sz;
+ bool be;
+ int err;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dat");
+ if (!r)
+ return -EINVAL;
+
+ sz = resource_size(r);
+
+ dat = bgpio_map(pdev, "dat", sz, &err);
+ if (!dat)
+ return err ? err : -EINVAL;
+
+ set = bgpio_map(pdev, "set", sz, &err);
+ if (err)
+ return err;
+
+ clr = bgpio_map(pdev, "clr", sz, &err);
+ if (err)
+ return err;
+
+ dirout = bgpio_map(pdev, "dirout", sz, &err);
+ if (err)
+ return err;
+
+ dirin = bgpio_map(pdev, "dirin", sz, &err);
+ if (err)
+ return err;
+
+ be = !strcmp(platform_get_device_id(pdev)->name, "basic-mmio-gpio-be");
+
+ return bgpio_probe(dev, sz, dat, set, clr, dirout, dirin, be);
+}
+
+static int __devexit bgpio_pdev_remove(struct platform_device *pdev)
+{
+ return bgpio_remove(&pdev->dev);
}
static const struct platform_device_id bgpio_id_table[] = {
@@ -506,8 +561,8 @@ static struct platform_driver bgpio_driver = {
.name = "basic-mmio-gpio",
},
.id_table = bgpio_id_table,
- .probe = bgpio_probe,
- .remove = __devexit_p(bgpio_remove),
+ .probe = bgpio_pdev_probe,
+ .remove = __devexit_p(bgpio_pdev_remove),
};
static int __init bgpio_init(void)
@@ -522,6 +577,8 @@ static void __exit bgpio_exit(void)
}
module_exit(bgpio_exit);
+#endif /* CONFIG_GPIO_BASIC_MMIO */
+
MODULE_DESCRIPTION("Driver for basic memory-mapped GPIO controllers");
MODULE_AUTHOR("Anton Vorontsov <[email protected]>");
MODULE_LICENSE("GPL");
diff --git a/include/linux/basic_mmio_gpio.h b/include/linux/basic_mmio_gpio.h
index f23ec73..f3a27e4 100644
--- a/include/linux/basic_mmio_gpio.h
+++ b/include/linux/basic_mmio_gpio.h
@@ -13,9 +13,24 @@
#ifndef __BASIC_MMIO_GPIO_H
#define __BASIC_MMIO_GPIO_H
+#include <linux/types.h>
+#include <linux/compiler.h>
+
struct bgpio_pdata {
int base;
int ngpio;
};
+struct device;
+
+int __devexit bgpio_remove(struct device *dev);
+int __devinit bgpio_probe(struct device *dev,
+ unsigned long sz,
+ void __iomem *dat,
+ void __iomem *set,
+ void __iomem *clr,
+ void __iomem *dirout,
+ void __iomem *dirin,
+ bool big_endian);
+
#endif /* __BASIC_MMIO_GPIO_H */
--
1.7.4.1
On Wed, 20 Apr 2011 00:30:31 +0400
Anton Vorontsov <[email protected]> wrote:
> On Tue, Apr 19, 2011 at 06:08:29PM +0100, Alan Cox wrote:
> > > How about exporting some bus/device-type neutral probe function, like
> > > we do in drivers/ata/pata_platform.c ?
> >
> > Not sure you want to be using resources as the basic currency, nor do you
> > want your generic code doing the request_region stuff. That was a nasty
> > mistake we made in the IDE code.
> >
> > Maybe the platform code should do that bit, but the generic stuff not - in
> > the PCI case the caller will have done pci_request_* itself and chances
> > are several of the resources are the same pci device resource and
> > different offsets so doing request_resource on them will blow up.
>
> Oh, right. How about this:
>
> int __devinit bgpio_probe(struct device *dev,
> unsigned long sz,
> void __iomem *dat,
> void __iomem *set,
> void __iomem *clr,
> void __iomem *dirout,
> void __iomem *dirin,
> bool big_endian);
>
> I.e. PCI driver can now map the whole area with a single ioremap(),
> and then call bgpio_probe(), which will provide accessors and
> common MMIO GPIO registration stuff.
Looks sane and providing its all using iomap/ioread/iowrite it covers all
cases nicely.
> > I.e. PCI driver can now map the whole area with a single ioremap(),
> > and then call bgpio_probe(), which will provide accessors and
> > common MMIO GPIO registration stuff.
>
> Looks sane and providing its all using iomap/ioread/iowrite it covers all
> cases nicely.
Actually not using iomap makes more sense on second thoughts, not all
platforms have a generic iomap implementation.
So yes this all looks good.
Hi Grant,
On Tue, 19 Apr 2011 08:44:29 -0600, Grant Likely wrote:
> On Tue, Apr 19, 2011 at 6:53 AM, Jean Delvare <[email protected]> wrote:
> > I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> > has an ICH10, I've added support for older ICH chips in case someone
> > needs it, as it was relatively simply to do that.
>
> Your timing is impeccable. You're getting caught up in the big gpio
> driver consolidation. :-)
>
> Most gpio drivers end up looking pretty darn similar. Instead of
> writing the same types of drivers over and over again, the new
> approach is to try and consolidate the mmio drivers down to using
> basic_mmio_gpio.c.
>
> In this particular case, you've got a PCI device which looks to be
> going into config space to get some information about how the chip is
> layed out. What I would do is keep your existing pci probe & remove
> hooks, but use them to create and register child basic_mmio_gpio
> platform_devices for each gpio bank.
I can see there are still discussions going on with regards to
basic_mmio_gpio. To be honest, I don't have any opinion on this. My
only concern is that I have driver code which appears to work well
enough for me and I would like it to be merged in kernel 2.6.40.
So my questions are as follows: what do I get to do for it to happen?
If there a chance that my driver as it currently exists (i.e. not using
basic_mmio_gpio) gets reviewed and merged? Or do I have to rewrite it
using basic_mmio_gpio to get a chance?
I can't see any driver currently relying on basic_mmio_gpio in the
kernel tree. Why is that, and why would my driver have to, if none else
did yet?
And a technical question (which makes me feel somewhat ashamed as I
guess I really should know the answer): the ICH is using I/O ports for
GPIO control, not a memory mapping. Would basic_mmio_gpio work for it
still?
Thanks,
--
Jean Delvare
> And a technical question (which makes me feel somewhat ashamed as I
> guess I really should know the answer): the ICH is using I/O ports for
> GPIO control, not a memory mapping. Would basic_mmio_gpio work for it
> still?
basic_mmio_gpio would need to use iomap for this and a lot of platforms
don't support generic iomap - so no it won't.
You don't generally want want to create sub platform devices anyway
without care as it makes sysfs, pci removal and power management ugly and
takes up *more* memory than repeating the code in the first places.
Alan
Hi Alan, Grant,
On Sat, 23 Apr 2011 15:47:07 +0100, Alan Cox wrote:
> > And a technical question (which makes me feel somewhat ashamed as I
> > guess I really should know the answer): the ICH is using I/O ports for
> > GPIO control, not a memory mapping. Would basic_mmio_gpio work for it
> > still?
>
> basic_mmio_gpio would need to use iomap for this and a lot of platforms
> don't support generic iomap - so no it won't.
>
> You don't generally want want to create sub platform devices anyway
> without care as it makes sysfs, pci removal and power management ugly and
> takes up *more* memory than repeating the code in the first places.
OK, so given that I can't use basic_mmio_gpio and Alan thinks that
having an independent driver is the way to go anyway, is there any
chance to get my code reviewed and merged? I am using it since the day
I submitted it (one month ago), it appears to work fine for me, and
it is a mandatory piece to support SMBus multiplexing on many x86 server
boards. I'd like to see it happen in kernel 2.6.40.
I can resend the patch if it helps, but I did not make any change to
the code since the first submission.
Thanks,
--
Jean Delvare
On Tue, Apr 19, 2011 at 02:53:03PM +0200, Jean Delvare wrote:
> I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> has an ICH10, I've added support for older ICH chips in case someone
> needs it, as it was relatively simply to do that.
>
> Signed-off-by: Jean Delvare <[email protected]>
> Cc: Grant Likely <[email protected]>
Applied, thanks
g.
> ---
> Note 1: On early ICH chips, some pins are exclusively inputs or
> outputs. The driver doesn't currently enforce this.
>
> Note 2: I'm not yet sure if we want a module alias for this driver.
> Many systems have the device but only a few of them will need the
> driver (and an ACPI resource conflict will be reported for many
> others, especially laptops I suspect.) So it might make more sense to
> let consumer drivers request the i801_gpio driver as needed (which they
> should do anyway, as you can't assume udev is always up and running on
> all systems.)
>
> Note 3: This is my first GPIO driver, so while it works fine for me, it
> might not be perfect. I welcome comments on how to improve it.
>
> drivers/gpio/Kconfig | 7
> drivers/gpio/Makefile | 1
> drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 440 insertions(+)
>
> --- linux-2.6.39-rc3.orig/drivers/gpio/Kconfig 2011-04-18 19:41:51.000000000 +0200
> +++ linux-2.6.39-rc3/drivers/gpio/Kconfig 2011-04-18 22:36:41.000000000 +0200
> @@ -322,6 +322,13 @@ config GPIO_BT8XX
>
> If unsure, say N.
>
> +config GPIO_I801
> + tristate "Intel 82801 (ICH) GPIO"
> + depends on PCI
> + help
> + This driver is for the GPIO pins of Intel 82801 south bridges
> + (aka ICH).
> +
> config GPIO_LANGWELL
> bool "Intel Langwell/Penwell GPIO support"
> depends on PCI && X86
> --- linux-2.6.39-rc3.orig/drivers/gpio/Makefile 2011-04-18 19:41:51.000000000 +0200
> +++ linux-2.6.39-rc3/drivers/gpio/Makefile 2011-04-18 22:36:41.000000000 +0200
> @@ -11,6 +11,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib.o
> obj-$(CONFIG_GPIO_ADP5520) += adp5520-gpio.o
> obj-$(CONFIG_GPIO_ADP5588) += adp5588-gpio.o
> obj-$(CONFIG_GPIO_BASIC_MMIO) += basic_mmio_gpio.o
> +obj-$(CONFIG_GPIO_I801) += i801_gpio.o
> obj-$(CONFIG_GPIO_LANGWELL) += langwell_gpio.o
> obj-$(CONFIG_GPIO_MAX730X) += max730x.o
> obj-$(CONFIG_GPIO_MAX7300) += max7300.o
> --- /dev/null 1970-01-01 00:00:00.000000000 +0000
> +++ linux-2.6.39-rc3/drivers/gpio/i801_gpio.c 2011-04-19 13:55:25.000000000 +0200
> @@ -0,0 +1,432 @@
> +/*
> + * Linux kernel driver for the Intel 82801 GPIO pins
> + * Copyright (C) 2011 Jean Delvare <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/acpi.h>
> +#include <linux/bitops.h>
> +#include <linux/gpio.h>
> +#include <linux/io.h>
> +#include <linux/ioport.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +
> +static int force;
> +module_param(force, int, 0444);
> +MODULE_PARM_DESC(force, "Forcibly enable access to GPIO");
> +
> +enum chips { ICH2, ICH4, ICH6, ICH10_CORP };
> +
> +/* Register definitions */
> +static const u8 REG_GPIO_USE_SEL[3] = { 0x00, 0x30, 0x40 };
> +static const u8 REG_GP_IO_SEL[3] = { 0x04, 0x34, 0x44 };
> +static const u8 REG_GP_LVL[3] = { 0x0C, 0x38, 0x48 };
> +
> +/**
> + * struct i801_gpio_data - 82801 GPIO private data
> + * @base: Base I/O port
> + * @io_size: I/O region size (64 or 128)
> + * @gpio: Data for GPIO infrastructure
> + * @lock: Mutex to serialize read-modify-write cycles
> + */
> +struct i801_gpio_data {
> + u32 base;
> + u32 io_size;
> + u32 use_sel[3];
> + struct gpio_chip gpio;
> + struct mutex lock;
> +};
> +
> +static int i801_gpio_request(struct gpio_chip *gpio, unsigned nr)
> +{
> + struct i801_gpio_data *data;
> + int group = nr >> 5;
> +
> + data = container_of(gpio, struct i801_gpio_data, gpio);
> + nr &= 0x1f;
> +
> + if (data->use_sel[group] & BIT(nr))
> + return 0;
> + else
> + return -EINVAL;
> +}
> +
> +static void i801_gpio_set(struct gpio_chip *gpio, unsigned nr, int val)
> +{
> + struct i801_gpio_data *data;
> + int group = nr >> 5;
> + u32 reg_val;
> +
> + data = container_of(gpio, struct i801_gpio_data, gpio);
> + nr &= 0x1f;
> +
> + mutex_lock(&data->lock);
> + reg_val = inl(data->base + REG_GP_LVL[group]);
> + if (val)
> + reg_val |= BIT(nr);
> + else
> + reg_val &= ~BIT(nr);
> + outl(reg_val, data->base + REG_GP_LVL[group]);
> + mutex_unlock(&data->lock);
> +}
> +
> +static int i801_gpio_get(struct gpio_chip *gpio, unsigned nr)
> +{
> + struct i801_gpio_data *data;
> + int group = nr >> 5;
> +
> + data = container_of(gpio, struct i801_gpio_data, gpio);
> + nr &= 0x1f;
> +
> + return (inl(data->base + REG_GP_LVL[group]) >> nr) & 1;
> +}
> +
> +static int i801_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
> + int val)
> +{
> + struct i801_gpio_data *data;
> + int group = nr >> 5;
> + u32 reg_val;
> +
> + data = container_of(gpio, struct i801_gpio_data, gpio);
> + nr &= 0x1f;
> +
> + mutex_lock(&data->lock);
> + reg_val = inl(data->base + REG_GP_IO_SEL[group]);
> + reg_val &= ~BIT(nr);
> + outl(reg_val, data->base + REG_GP_IO_SEL[group]);
> +
> + reg_val = inl(data->base + REG_GP_LVL[group]);
> + if (val)
> + reg_val |= BIT(nr);
> + else
> + reg_val &= ~BIT(nr);
> + outl(reg_val, data->base + REG_GP_LVL[group]);
> + mutex_unlock(&data->lock);
> +
> + return 0;
> +}
> +
> +static int i801_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
> +{
> + struct i801_gpio_data *data;
> + int group = nr >> 5;
> + u32 reg_val;
> +
> + data = container_of(gpio, struct i801_gpio_data, gpio);
> + nr &= 0x1f;
> +
> + mutex_lock(&data->lock);
> + reg_val = inl(data->base + REG_GP_IO_SEL[group]);
> + reg_val |= BIT(nr);
> + outl(reg_val, data->base + REG_GP_IO_SEL[group]);
> + mutex_unlock(&data->lock);
> +
> + return 0;
> +}
> +
> +static void __devinit i801_gpio_setup(struct gpio_chip *gpio,
> + struct device *dev, int ngpio)
> +{
> + gpio->label = "i801_gpio";
> + gpio->dev = dev;
> + gpio->owner = THIS_MODULE;
> + gpio->request = i801_gpio_request;
> + gpio->direction_input = i801_gpio_direction_input;
> + gpio->get = i801_gpio_get;
> + gpio->direction_output = i801_gpio_direction_output;
> + gpio->set = i801_gpio_set;
> + gpio->base = -1;
> + gpio->ngpio = ngpio;
> +}
> +
> +/*
> + * On the ICH/ICH0, ICH3, ICH4 and ICH5, some selection bits control more
> + * than one GPIO.
> + */
> +static void __devinit i801_gpio_sel_fixup(struct i801_gpio_data *data,
> + u32 device)
> +{
> + switch (device) {
> + case PCI_DEVICE_ID_INTEL_82801AA_0:
> + case PCI_DEVICE_ID_INTEL_82801AB_0:
> + case PCI_DEVICE_ID_INTEL_82801CA_0:
> + case PCI_DEVICE_ID_INTEL_82801CA_12:
> + case PCI_DEVICE_ID_INTEL_82801DB_0:
> + case PCI_DEVICE_ID_INTEL_82801DB_12:
> + case PCI_DEVICE_ID_INTEL_82801EB_0:
> + if (data->use_sel[0] & BIT(0))
> + data->use_sel[0] |= BIT(16);
> + if (data->use_sel[0] & BIT(1))
> + data->use_sel[0] |= BIT(17);
> + if (data->use_sel[0] & BIT(27))
> + data->use_sel[0] |= BIT(28);
> + break;
> + }
> +}
> +
> +static __devinitdata
> +const struct {
> + int ngroup;
> + int io_size;
> + u8 reg_gpiobase;
> + u8 reg_gc;
> +} i801_gpio_cfg[] = {
> + [ICH2] = { 1, 64, 0x58, 0x5C },
> + [ICH4] = { 2, 64, 0x58, 0x5C },
> + [ICH6] = { 2, 64, 0x48, 0x4C },
> + [ICH10_CORP] = { 3, 128, 0x48, 0x4C },
> +};
> +
> +static void __devinit i801_gpio_print_state(struct i801_gpio_data *data,
> + struct device *dev, int ngroup)
> +{
> + int i, group;
> + u32 io_sel, lvl;
> +
> + dev_dbg(dev, "Current state of GPIO pins:\n");
> + for (group = 0; group < ngroup; group++) {
> + io_sel = inl(data->base + REG_GP_IO_SEL[group]);
> + lvl = inl(data->base + REG_GP_LVL[group]);
> +
> + for (i = 0; i < 32; i++) {
> + if (!(data->use_sel[group] & BIT(i)))
> + continue;
> +
> + dev_dbg(dev, "GPIO%d: %s, level %d\n", group * 32 + i,
> + io_sel & BIT(i) ? "input" : "output",
> + !!(lvl & BIT(i)));
> + }
> + }
> +}
> +
> +static int __devinit i801_gpio_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + struct i801_gpio_data *data;
> + u32 gpiobase;
> + u8 gc;
> + int type = id->driver_data;
> + int group, ngroup, err;
> +
> + data = kzalloc(sizeof(struct i801_gpio_data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + err = pci_enable_device(pdev);
> + if (err) {
> + dev_err(&pdev->dev, "Failed to enable device (%d)\n", err);
> + goto err_free;
> + }
> +
> + /* Get the base I/O address */
> + err = pci_read_config_dword(pdev, i801_gpio_cfg[type].reg_gpiobase,
> + &gpiobase);
> + if (err) {
> + dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
> + "GPIOBASE", err);
> + goto err_disable;
> + }
> + data->base = gpiobase & ~BIT(0);
> + if (!data->base) {
> + dev_err(&pdev->dev, "GPIOBASE not set\n");
> + err = -ENODEV;
> + goto err_disable;
> + }
> +
> + /* Check configuration */
> + err = pci_read_config_byte(pdev, i801_gpio_cfg[type].reg_gc, &gc);
> + if (err) {
> + dev_err(&pdev->dev, "Couldn't read %s value (%d)\n",
> + "GC", err);
> + goto err_disable;
> + }
> + if (gc & BIT(0)) {
> + dev_err(&pdev->dev, "GPIO function is %s\n", "locked");
> + err = -EBUSY;
> + goto err_disable;
> + }
> + if (!(gc & BIT(4))) {
> + if (!force) {
> + dev_err(&pdev->dev, "GPIO function is %s\n",
> + "disabled");
> + err = -ENODEV;
> + goto err_disable;
> + }
> +
> + gc |= BIT(4);
> + err = pci_write_config_byte(pdev,
> + i801_gpio_cfg[type].reg_gc, gc);
> + if (err) {
> + dev_err(&pdev->dev,
> + "Failed to enable GPIO function (%d)\n",
> + err);
> + err = -ENODEV;
> + goto err_disable;
> + }
> + dev_info(&pdev->dev, "Enabling GPIO function\n");
> + }
> +
> + /*
> + * "Corporate" incarnations of the ICH10 have an I/O region of size
> + * 128 and 73 GPIO pins. Others ("consumer") have an I/O region of
> + * size only 64 and 61 GPIO pins, as the ICH6, ICH7, ICH8 and ICH9
> + * had.
> + */
> + data->io_size = i801_gpio_cfg[type].io_size;
> + if (!force) {
> + err = acpi_check_region(data->base, data->io_size, "i801_gpio");
> + if (err)
> + goto err_disable;
> + }
> + if (!request_region(data->base, data->io_size, "i801_gpio")) {
> + dev_err(&pdev->dev, "Failed to request I/O ports (%d)", err);
> + err = -EBUSY;
> + goto err_disable;
> + }
> +
> + ngroup = i801_gpio_cfg[type].ngroup;
> + for (group = 0; group < ngroup; group++)
> + data->use_sel[group] = inl(data->base +
> + REG_GPIO_USE_SEL[group]);
> + i801_gpio_sel_fixup(data, id->device);
> + mutex_init(&data->lock);
> +
> + i801_gpio_setup(&data->gpio, &pdev->dev, ngroup * 32);
> + i801_gpio_print_state(data, &pdev->dev, ngroup);
> +
> + pci_set_drvdata(pdev, data);
> + err = gpiochip_add(&data->gpio);
> + if (err) {
> + dev_err(&pdev->dev, "Failed to register GPIO (%d)\n", err);
> + goto err_release;
> + }
> +
> + return 0;
> +
> + err_release:
> + release_region(data->base, data->io_size);
> +
> + err_disable:
> + pci_disable_device(pdev);
> +
> + err_free:
> + kfree(data);
> + return err;
> +}
> +
> +static void __devexit i801_gpio_remove(struct pci_dev *pdev)
> +{
> + struct i801_gpio_data *data = pci_get_drvdata(pdev);
> + int err;
> +
> + err = gpiochip_remove(&data->gpio);
> + if (err)
> + dev_err(&pdev->dev, "Failed to unregister GPIO (%d)\n", err);
> +
> + release_region(data->base, data->io_size);
> + pci_disable_device(pdev);
> + kfree(data);
> +}
> +
> +/* driver_data is the number of GPIO groups */
> +static DEFINE_PCI_DEVICE_TABLE(i801_gpio_pcidev_id) = {
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0),
> + .driver_data = ICH2 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0),
> + .driver_data = ICH2 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0),
> + .driver_data = ICH2 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10),
> + .driver_data = ICH2 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0),
> + .driver_data = ICH4 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12),
> + .driver_data = ICH4 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0),
> + .driver_data = ICH4 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12),
> + .driver_data = ICH4 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0),
> + .driver_data = ICH4 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_1),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_4),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_1),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_4),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_5),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_7),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_8),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_0),
> + .driver_data = ICH10_CORP },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_1),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_2),
> + .driver_data = ICH6 },
> + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH10_3),
> + .driver_data = ICH10_CORP },
> + { 0, }
> +};
> +MODULE_DEVICE_TABLE(pci, i801_gpio_pcidev_id);
> +
> +static struct pci_driver i801_gpio_driver = {
> + .name = "i801_gpio",
> + .id_table = i801_gpio_pcidev_id,
> + .probe = i801_gpio_probe,
> + .remove = __devexit_p(i801_gpio_remove),
> +};
> +
> +static int __init i801_gpio_pci_init(void)
> +{
> + return pci_register_driver(&i801_gpio_driver);
> +}
> +module_init(i801_gpio_pci_init);
> +
> +static void __exit i801_gpio_pci_exit(void)
> +{
> + pci_unregister_driver(&i801_gpio_driver);
> +}
> +module_exit(i801_gpio_pci_exit);
> +
> +MODULE_AUTHOR("Jean Delvare <[email protected]>");
> +MODULE_DESCRIPTION("Intel 82801 (ICH) GPIO driver");
> +MODULE_LICENSE("GPL");
>
>
> --
> Jean Delvare
On Tue, Apr 19, 2011 at 08:53:03AM -0400, Jean Delvare wrote:
> I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> has an ICH10, I've added support for older ICH chips in case someone
> needs it, as it was relatively simply to do that.
>
> Signed-off-by: Jean Delvare <[email protected]>
> Cc: Grant Likely <[email protected]>
> ---
> Note 1: On early ICH chips, some pins are exclusively inputs or
> outputs. The driver doesn't currently enforce this.
>
> Note 2: I'm not yet sure if we want a module alias for this driver.
> Many systems have the device but only a few of them will need the
> driver (and an ACPI resource conflict will be reported for many
> others, especially laptops I suspect.) So it might make more sense to
> let consumer drivers request the i801_gpio driver as needed (which they
> should do anyway, as you can't assume udev is always up and running on
> all systems.)
>
> Note 3: This is my first GPIO driver, so while it works fine for me, it
> might not be perfect. I welcome comments on how to improve it.
>
> drivers/gpio/Kconfig | 7
> drivers/gpio/Makefile | 1
> drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 440 insertions(+)
>
Did this or an alternate patch for gpio support on the recent Intel ICHs go anywhere ?
I found another patch which is using a different approach, but I don't see anything
in the latest kernel.
Thanks,
Guenter
On Wed, 1 Feb 2012 18:31:29 -0800, Guenter Roeck wrote:
> On Tue, Apr 19, 2011 at 08:53:03AM -0400, Jean Delvare wrote:
> > I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> > has an ICH10, I've added support for older ICH chips in case someone
> > needs it, as it was relatively simply to do that.
> >
> > Signed-off-by: Jean Delvare <[email protected]>
> > Cc: Grant Likely <[email protected]>
> > ---
> > Note 1: On early ICH chips, some pins are exclusively inputs or
> > outputs. The driver doesn't currently enforce this.
> >
> > Note 2: I'm not yet sure if we want a module alias for this driver.
> > Many systems have the device but only a few of them will need the
> > driver (and an ACPI resource conflict will be reported for many
> > others, especially laptops I suspect.) So it might make more sense to
> > let consumer drivers request the i801_gpio driver as needed (which they
> > should do anyway, as you can't assume udev is always up and running on
> > all systems.)
> >
> > Note 3: This is my first GPIO driver, so while it works fine for me, it
> > might not be perfect. I welcome comments on how to improve it.
> >
> > drivers/gpio/Kconfig | 7
> > drivers/gpio/Makefile | 1
> > drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 440 insertions(+)
>
> Did this or an alternate patch for gpio support on the recent Intel ICHs go anywhere ?
>
> I found another patch which is using a different approach, but I don't see anything
> in the latest kernel.
I don't think any driver made it into the kernel, at least I can't find
any in 3.3-rc2 nor linux-next. I'm not surprised, as neither driver was
using the MFD framework while this would really be the right thing to
do here. I never took the time to convert my driver to MFD and
apparently Peter did not either. This is still on my to-do list but you
know how long it is. If anyone is faster than me at getting the code is
a shape suitable for upstream, I'll be happy to help with review and
testing.
--
Jean Delvare
On Thu, 2012-02-02 at 02:49 -0500, Jean Delvare wrote:
> On Wed, 1 Feb 2012 18:31:29 -0800, Guenter Roeck wrote:
> > On Tue, Apr 19, 2011 at 08:53:03AM -0400, Jean Delvare wrote:
> > > I need this to handle SMBus multiplexing on my Asus Z8NA-D6 board. It
> > > has an ICH10, I've added support for older ICH chips in case someone
> > > needs it, as it was relatively simply to do that.
> > >
> > > Signed-off-by: Jean Delvare <[email protected]>
> > > Cc: Grant Likely <[email protected]>
> > > ---
> > > Note 1: On early ICH chips, some pins are exclusively inputs or
> > > outputs. The driver doesn't currently enforce this.
> > >
> > > Note 2: I'm not yet sure if we want a module alias for this driver.
> > > Many systems have the device but only a few of them will need the
> > > driver (and an ACPI resource conflict will be reported for many
> > > others, especially laptops I suspect.) So it might make more sense to
> > > let consumer drivers request the i801_gpio driver as needed (which they
> > > should do anyway, as you can't assume udev is always up and running on
> > > all systems.)
> > >
> > > Note 3: This is my first GPIO driver, so while it works fine for me, it
> > > might not be perfect. I welcome comments on how to improve it.
> > >
> > > drivers/gpio/Kconfig | 7
> > > drivers/gpio/Makefile | 1
> > > drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> > > 3 files changed, 440 insertions(+)
> >
> > Did this or an alternate patch for gpio support on the recent Intel ICHs go anywhere ?
> >
> > I found another patch which is using a different approach, but I don't see anything
> > in the latest kernel.
>
> I don't think any driver made it into the kernel, at least I can't find
> any in 3.3-rc2 nor linux-next. I'm not surprised, as neither driver was
> using the MFD framework while this would really be the right thing to
> do here. I never took the time to convert my driver to MFD and
> apparently Peter did not either. This is still on my to-do list but you
> know how long it is. If anyone is faster than me at getting the code is
> a shape suitable for upstream, I'll be happy to help with review and
> testing.
>
I'd love to take this on, but unfortunately my task list isn't getting
shorter either. Just bad that this doesn't seem to make it in.
One reason of course may be that at least Peter's version tried to
accomplish too much. Looking through the comments, seems there was a
disagreement on unrelated issues such as if there should be a new
"unknown" gpio direction or not. Maybe it would make more sense to
separate the core patch to add ICH gpio support from the rest of Peter's
proposed changes.
Peter, do you have any plans to work on this in the near future ?
Thanks,
Guenter
<snip>
> > > > drivers/gpio/Kconfig | 7
> > > > drivers/gpio/Makefile | 1
> > > > drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > 3 files changed, 440 insertions(+)
> > >
> > > Did this or an alternate patch for gpio support on the recent Intel ICHs go anywhere ?
> > >
> > > I found another patch which is using a different approach, but I don't see anything
> > > in the latest kernel.
> >
> > I don't think any driver made it into the kernel, at least I can't find
> > any in 3.3-rc2 nor linux-next. I'm not surprised, as neither driver was
> > using the MFD framework while this would really be the right thing to
> > do here. I never took the time to convert my driver to MFD and
> > apparently Peter did not either. This is still on my to-do list but you
> > know how long it is. If anyone is faster than me at getting the code is
> > a shape suitable for upstream, I'll be happy to help with review and
> > testing.
> >
> I'd love to take this on, but unfortunately my task list isn't getting
> shorter either. Just bad that this doesn't seem to make it in.
>
> One reason of course may be that at least Peter's version tried to
> accomplish too much. Looking through the comments, seems there was a
> disagreement on unrelated issues such as if there should be a new
> "unknown" gpio direction or not. Maybe it would make more sense to
> separate the core patch to add ICH gpio support from the rest of Peter's
> proposed changes.
I agree with Jean, I believe the main issue it wasn't accepted was the
fact that it didn't use the MFD framework. The driver I submitted
follows the model of other drivers that share the same PCI device (iTCO,
esb2rom, etc), so is functionally OK to use as is, it just doesn't use
the recommend MFD framework, which was gating its acceptance.
> Peter, do you have any plans to work on this in the near future ?
I too have been caught up in other tasks. A co-worker recently created
a patch to transition the ICHx gpio driver I wrote and other related
drivers (esb2rom, iTCO, etc) to the MFD structure with the intention
that it would eventually be merged upstream. The patch is still under
review internally, but I'll talk to him about submitting it upstream.
Best,
Peter
On Thu, 2012-02-02 at 14:56 -0500, Peter Tyser wrote:
> <snip>
>
> > > > > drivers/gpio/Kconfig | 7
> > > > > drivers/gpio/Makefile | 1
> > > > > drivers/gpio/i801_gpio.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
> > > > > 3 files changed, 440 insertions(+)
> > > >
> > > > Did this or an alternate patch for gpio support on the recent Intel ICHs go anywhere ?
> > > >
> > > > I found another patch which is using a different approach, but I don't see anything
> > > > in the latest kernel.
> > >
> > > I don't think any driver made it into the kernel, at least I can't find
> > > any in 3.3-rc2 nor linux-next. I'm not surprised, as neither driver was
> > > using the MFD framework while this would really be the right thing to
> > > do here. I never took the time to convert my driver to MFD and
> > > apparently Peter did not either. This is still on my to-do list but you
> > > know how long it is. If anyone is faster than me at getting the code is
> > > a shape suitable for upstream, I'll be happy to help with review and
> > > testing.
> > >
> > I'd love to take this on, but unfortunately my task list isn't getting
> > shorter either. Just bad that this doesn't seem to make it in.
> >
> > One reason of course may be that at least Peter's version tried to
> > accomplish too much. Looking through the comments, seems there was a
> > disagreement on unrelated issues such as if there should be a new
> > "unknown" gpio direction or not. Maybe it would make more sense to
> > separate the core patch to add ICH gpio support from the rest of Peter's
> > proposed changes.
>
> I agree with Jean, I believe the main issue it wasn't accepted was the
> fact that it didn't use the MFD framework. The driver I submitted
> follows the model of other drivers that share the same PCI device (iTCO,
> esb2rom, etc), so is functionally OK to use as is, it just doesn't use
> the recommend MFD framework, which was gating its acceptance.
>
> > Peter, do you have any plans to work on this in the near future ?
>
> I too have been caught up in other tasks. A co-worker recently created
> a patch to transition the ICHx gpio driver I wrote and other related
> drivers (esb2rom, iTCO, etc) to the MFD structure with the intention
> that it would eventually be merged upstream. The patch is still under
> review internally, but I'll talk to him about submitting it upstream.
Would you be willing to share your current code ? Even if it is too
early for upstream submission, you might get some testing and review
feedback.
Thanks,
Guenter
This driver works on many Intel chipsets, including the ICH6, ICH7,
ICH8, ICH9, ICH10, 3100, Series 5/3400 (Ibex Peak), Series 6/C200
(Cougar Point), and NM10 (Tiger Point).
Additional Intel chipsets should be easily supported if needed, eg the
ICH1-5, EP80579, etc.
Tested on QM67 (Cougar Point), QM57 (Ibex Peak), 3100 (Whitmore Lake),
and NM10 (Tiger Point).
Signed-off-by: Peter Tyser <[email protected]>
---
MAINTAINERS | 6 +
drivers/gpio/Kconfig | 16 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/ich_gpio.c | 433 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 456 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/ich_gpio.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 5738c8b..4905dd9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3160,6 +3160,12 @@ W: http://www.developer.ibm.com/welcome/netfinity/serveraid.html
S: Supported
F: drivers/scsi/ips.*
+ICH LPC DRIVER
+M: Peter Tyser <[email protected]>
+S: Maintained
+F: drivers/mfd/ich_lpc.c
+F: drivers/gpio/ich_gpio.c
+
IDE SUBSYSTEM
M: "David S. Miller" <[email protected]>
L: [email protected]
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 2967002..5ae654d 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -140,6 +140,22 @@ config GPIO_SCH
This driver can also be built as a module. If so, the module
will be called sch-gpio.
+config GPIO_ICH
+ tristate "Intel ICH GPIO"
+ depends on PCI && X86
+ select MFD_CORE
+ select LPC_ICH
+ help
+ Say yes here to support the GPIO functionality of a number of Intel
+ ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
+ ICH9, ICH10, Series 5/3400 (eg Ibex Peak), Series 6/C200 (eg
+ Cougar Point), NM10 (Tiger Point), and 3100 (Whitmore Lake).
+
+ If unsure, say N.
+
+ This driver can also be built as a module. If so, the module
+ will be called ich-gpio.
+
config GPIO_VX855
tristate "VIA VX855/VX875 GPIO"
depends on MFD_SUPPORT && PCI
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index b605f8e..840a654 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o
obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
+obj-$(CONFIG_GPIO_ICH) += ich_gpio.o
obj-$(CONFIG_MACH_U300) += gpio-u300.o
obj-$(CONFIG_PLAT_NOMADIK) += gpio-nomadik.o
obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
diff --git a/drivers/gpio/ich_gpio.c b/drivers/gpio/ich_gpio.c
new file mode 100644
index 0000000..f7feca1
--- /dev/null
+++ b/drivers/gpio/ich_gpio.c
@@ -0,0 +1,433 @@
+/*
+ * Intel ICH6-10, Series 5 and 6 GPIO driver
+ *
+ * Copyright (C) 2010 Extreme Engineering Solutions.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/lpc_ich.h>
+
+#define DRV_NAME "ich_gpio"
+
+/* PCI config register offsets into LPC I/F - D31:F0 */
+#define PCI_ICHX_ACPI_BAR 0x40
+#define PCI_ICHX_GPIO_BAR 0x48
+#define PCI_ICHX_GPIO_CTRL 0x4C
+
+/*
+ * GPIO register offsets in GPIO I/O space.
+ * Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and
+ * LVLx registers. Logic in the read/write functions takes a register and
+ * an absolute bit number and determines the proper register offset and bit
+ * number in that register. For example, to read the value of GPIO bit 50
+ * the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)],
+ * bit 18 (50%32).
+ */
+enum GPIO_REG {
+ GPIO_USE_SEL = 0,
+ GPIO_IO_SEL,
+ GPIO_LVL,
+};
+
+static const u8 ichx_regs[3][3] = {
+ {0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */
+ {0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */
+ {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */
+};
+
+#define ICHX_GPIO_WRITE(val, reg) outl(val, (reg) + ichx_priv.gpio_base.start)
+#define ICHX_GPIO_READ(reg) inl((reg) + ichx_priv.gpio_base.start)
+#define ICHX_PM_WRITE(val, reg) outl(val, (reg) + ichx_priv.pm_base.start)
+#define ICHX_PM_READ(reg) inl((reg) + ichx_priv.pm_base.start)
+
+/* Convenient wrapper to make our PCI ID table */
+#define ICHX_GPIO_PCI_DEVICE(dev, data) \
+ .vendor = PCI_VENDOR_ID_INTEL, \
+ .device = dev, \
+ .subvendor = PCI_ANY_ID, \
+ .subdevice = PCI_ANY_ID, \
+ .class = 0, \
+ .class_mask = 0, \
+ .driver_data = (ulong)data
+
+struct ichx_desc {
+ /* Max GPIO pins the chipset can have */
+ uint ngpio;
+
+ /* The offset of GPE0_STS in the PM IO region, 0 if unneeded */
+ uint gpe0_sts_ofs;
+
+ /* USE_SEL is bogus on some chipsets, eg 3100 */
+ u32 use_sel_ignore[3];
+
+ /* Some chipsets have quirks, let these use their own request/get */
+ int (*request)(struct gpio_chip *chip, unsigned offset);
+ int (*get)(struct gpio_chip *chip, unsigned offset);
+};
+
+static struct {
+ spinlock_t lock;
+ struct platform_device *dev;
+ struct pci_dev *pdev;
+ struct gpio_chip chip;
+ struct resource gpio_base; /* GPIO IO base */
+ struct resource pm_base; /* Power Mangagment IO base */
+ struct ichx_desc *desc; /* Pointer to chipset-specific description */
+ u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */
+} ichx_priv;
+
+static int modparam_gpiobase = -1; /* dynamic */
+module_param_named(gpiobase, modparam_gpiobase, int, 0444);
+MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, "
+ "which is the default.");
+
+static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
+{
+ unsigned long flags;
+ u32 data;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+ int ret = 0;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ data = ICHX_GPIO_READ(ichx_regs[reg][reg_nr]);
+ data = (data & ~(1 << bit)) | (val << bit);
+ ICHX_GPIO_WRITE(data, ichx_regs[reg][reg_nr]);
+ if (verify && (data != ICHX_GPIO_READ(ichx_regs[reg][reg_nr])))
+ ret = -EPERM;
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return ret;
+}
+
+static int ichx_read_bit(int reg, unsigned nr)
+{
+ unsigned long flags;
+ u32 data;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ data = ICHX_GPIO_READ(ichx_regs[reg][reg_nr]);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return data & (1 << bit) ? 1 : 0;
+}
+
+static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ /*
+ * Try setting pin as an input and verify it worked since many pins
+ * are output-only.
+ */
+ if (ichx_write_bit(GPIO_IO_SEL, nr, 1, 1))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
+ int val)
+{
+ /* Set GPIO output value. */
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+
+ /*
+ * Try setting pin as an output and verify it worked since many pins
+ * are input-only.
+ */
+ if (ichx_write_bit(GPIO_IO_SEL, nr, 0, 1))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr)
+{
+ return ichx_read_bit(GPIO_LVL, nr);
+}
+
+static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr)
+{
+ unsigned long flags;
+ u32 data;
+
+ /*
+ * GPI 0 - 15 need to be read from the power management registers on
+ * a ICH6/3100 bridge.
+ */
+ if (nr < 16) {
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ /* GPI 0 - 15 are latched, write 1 to clear*/
+ ICHX_PM_WRITE(1 << (16 + nr), ichx_priv.desc->gpe0_sts_ofs);
+
+ data = ICHX_PM_READ(ichx_priv.desc->gpe0_sts_ofs);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return (data >> 16) & (1 << nr) ? 1 : 0;
+ } else {
+ return ichx_gpio_get(chip, nr);
+ }
+}
+
+static int ichx_gpio_request(struct gpio_chip *chip, unsigned nr)
+{
+ /*
+ * Note we assume the BIOS properly set a bridge's USE value. Some
+ * chips (eg Intel 3100) have bogus USE values though, so first see if
+ * the chipset's USE value can be trusted for this specific bit.
+ * If it can't be trusted, assume that the pin can be used as a GPIO.
+ */
+ if (ichx_priv.desc->use_sel_ignore[nr / 32] & (1 << (nr & 0x1f)))
+ return 1;
+
+ return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
+}
+
+static int ich6_gpio_request(struct gpio_chip *chip, unsigned nr)
+{
+ /*
+ * Fixups for bits 16 and 17 are necessary on the Intel ICH6/3100
+ * bridge as they are controlled by USE register bits 0 and 1. See
+ * "Table 704 GPIO_USE_SEL1 register" in the i3100 datasheet for
+ * additional info.
+ */
+ if ((nr == 16) || (nr == 17))
+ nr -= 16;
+
+ return ichx_gpio_request(chip, nr);
+}
+
+static void ichx_gpio_set(struct gpio_chip *chip, unsigned nr, int val)
+{
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+}
+
+static void __devinit ichx_gpiolib_setup(struct gpio_chip *chip)
+{
+ chip->owner = THIS_MODULE;
+ chip->label = DRV_NAME;
+
+ /* Allow chip-specific overrides of request()/get() */
+ chip->request = ichx_priv.desc->request ?
+ ichx_priv.desc->request : ichx_gpio_request;
+ chip->get = ichx_priv.desc->get ?
+ ichx_priv.desc->get : ichx_gpio_get;
+
+ chip->set = ichx_gpio_set;
+ chip->direction_input = ichx_gpio_direction_input;
+ chip->direction_output = ichx_gpio_direction_output;
+ chip->base = modparam_gpiobase;
+ chip->ngpio = ichx_priv.desc->ngpio;
+ chip->can_sleep = 0;
+ chip->dbg_show = NULL;
+}
+
+/* ICH6-based, 631xesb-based */
+static struct ichx_desc ich6_desc = {
+ /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .gpe0_sts_ofs = 0x28,
+
+ .ngpio = 50,
+};
+
+/* Intel 3100 */
+static struct ichx_desc i3100_desc = {
+ /*
+ * Bits 16,17, 20 of USE_SEL and bit 16 of USE_SEL2 always read 0 on
+ * the Intel 3100. See "Table 712. GPIO Summary Table" of 3100
+ * Datasheet for more info.
+ */
+ .use_sel_ignore = {0x00130000, 0x00010000, 0x0},
+
+ /* The 3100 needs fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .gpe0_sts_ofs = 0x28,
+
+ .ngpio = 50,
+};
+
+/* ICH7 and ICH8-based */
+static struct ichx_desc ich7_desc = {
+ .ngpio = 50,
+};
+
+/* ICH9-based */
+static struct ichx_desc ich9_desc = {
+ .ngpio = 61,
+};
+
+/* ICH10-based - Consumer/corporate versions have different amount of GPIO */
+static struct ichx_desc ich10_cons_desc = {
+ .ngpio = 51,
+};
+static struct ichx_desc ich10_corp_desc = {
+ .ngpio = 72,
+};
+
+/* Intel 5 series, 6 series, 3400 series, and C200 series */
+static struct ichx_desc intel5_desc = {
+ .ngpio = 76,
+};
+
+static int __devinit ichx_gpio_probe(struct platform_device *pdev)
+{
+ struct resource *res_base, *res_pm;
+ size_t sz_base;
+ int id;
+ int err;
+
+ id = pdev->id;
+ if (!id)
+ return -ENODEV;
+
+ switch (lpc_chipset_info[id].gpio_version) {
+ case 0x401:
+ ichx_priv.desc = &i3100_desc;
+ break;
+ case 0x501:
+ ichx_priv.desc = &intel5_desc;
+ break;
+ case 0x601:
+ ichx_priv.desc = &ich6_desc;
+ break;
+ case 0x701:
+ ichx_priv.desc = &ich7_desc;
+ break;
+ case 0x801:
+ ichx_priv.desc = &ich9_desc;
+ break;
+ case 0xa01:
+ ichx_priv.desc = &ich10_corp_desc;
+ break;
+ case 0xa11:
+ ichx_priv.desc = &ich10_cons_desc;
+ break;
+ default:
+ goto base_err;
+ }
+
+ res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
+ if (!res_base)
+ goto base_err;
+
+ sz_base = ichx_priv.desc->ngpio > 64 ? resource_size(res_base) : 64;
+ if (!request_region(res_base->start, sz_base, pdev->name))
+ goto base_err;
+
+ memcpy(&ichx_priv.gpio_base, res_base, sizeof(struct resource));
+ ichx_priv.gpio_base.end = ichx_priv.gpio_base.end + sz_base;
+
+ /*
+ * If necessary, determine the I/O address of ACPI/power management
+ * registers which are needed to read the the GPE0 register for GPI pins
+ * 0 - 15 on some chipsets.
+ */
+ if (!ichx_priv.desc->gpe0_sts_ofs)
+ goto init;
+
+ res_pm = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPE0);
+ if (!res_pm) {
+ pr_warn("ACPI BAR not enumerated, GPI 0 - 15 "
+ "unusable\n");
+ goto init;
+ }
+
+ if (!request_region(res_pm->start, resource_size(res_pm),
+ pdev->name)) {
+ pr_warn("ACPI BAR not enumerated, GPI 0 - 15 "
+ "unusable\n");
+ goto init;
+ }
+
+ memcpy(&ichx_priv.pm_base, res_pm, sizeof(struct resource));
+
+init:
+ ichx_gpiolib_setup(&ichx_priv.chip);
+ err = gpiochip_add(&ichx_priv.chip);
+ if (err) {
+ pr_err("Failed to register GPIOs\n");
+ return err;
+ }
+
+ pr_info("GPIO from %d to %d on %s\n", ichx_priv.chip.base,
+ ichx_priv.chip.base + ichx_priv.chip.ngpio - 1, DRV_NAME);
+
+ return 0;
+
+base_err:
+ return -ENODEV;
+}
+
+static int __devexit ichx_gpio_remove(struct platform_device *pdev)
+{
+ int err;
+
+ err = gpiochip_remove(&ichx_priv.chip);
+ if (err)
+ dev_err(&pdev->dev, "%s failed, %d\n",
+ "gpiochip_remove()", err);
+
+ err = release_resource(&ichx_priv.gpio_base);
+
+ return err;
+}
+
+static struct platform_driver ichx_gpio_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ },
+ .probe = ichx_gpio_probe,
+ .remove = __devexit_p(ichx_gpio_remove),
+};
+
+static int __devinit ichx_gpio_init_module(void)
+{
+ return platform_driver_register(&ichx_gpio_driver);
+}
+
+static void __devexit ichx_gpio_exit_module(void)
+{
+ platform_driver_unregister(&ichx_gpio_driver);
+}
+
+module_init(ichx_gpio_init_module);
+module_exit(ichx_gpio_exit_module);
+
+MODULE_AUTHOR("Peter Tyser <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for Intel ICH series");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ich_gpio");
--
1.7.0.4
This patch converts the iTCO_wdt driver to use the multi-function device
driver model. It uses resources discovered by the lpc_ich driver, so that
it no longer does its own PCI scanning.
The driver has also been modernized to use pr_info and the like.
Signed-off-by: Aaron Sierra <[email protected]>
---
drivers/mfd/Kconfig | 3 +-
drivers/mfd/lpc_ich.c | 297 ++++++++++-------
drivers/watchdog/Kconfig | 1 +
drivers/watchdog/iTCO_vendor.h | 6 +-
drivers/watchdog/iTCO_vendor_support.c | 43 +--
drivers/watchdog/iTCO_wdt.c | 596 +++++---------------------------
include/linux/mfd/lpc_ich.h | 7 +
7 files changed, 295 insertions(+), 658 deletions(-)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 18eca82..af42bb8 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -643,7 +643,8 @@ config LPC_ICH
help
The LPC bridge function of the Intel ICH provides support for
many functional units. This driver provides needed support for
- other drivers to control these functions, currently GPIO.
+ other drivers to control these functions, currently GPIO and
+ watchdog.
config MFD_RDC321X
tristate "Support for RDC-R321x southbridge"
diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
index a288c74..b45c0e1 100644
--- a/drivers/mfd/lpc_ich.c
+++ b/drivers/mfd/lpc_ich.c
@@ -47,14 +47,42 @@
#define ACPIBASE 0x40
#define ACPIBASE_GPE_OFF 0x20
#define ACPIBASE_GPE_END 0x2f
+#define ACPIBASE_SMI_OFF 0x30
+#define ACPIBASE_SMI_END 0x33
+#define ACPIBASE_TCO_OFF 0x60
+#define ACPIBASE_TCO_END 0x7f
#define ACPICTRL 0x44
+#define ACPIBASE_GCS_OFF 0x3410
+#define ACPIBASE_GCS_END 0x3414
+
#define GPIOBASE 0x48
#define GPIOCTRL 0x4C
#define GPIOBASE_IO_SIZE 0x80
+#define RCBABASE 0xf0
+
+#define wdt_io_res(i) wdt_res(0, i)
+#define wdt_mem_res(i) wdt_res(ICH_RES_MEM_OFF, i)
+#define wdt_res(b, i) (&wdt_ich_res[(b) + (i)])
+
static u8 lpc_ich_acpi_save, lpc_ich_gpio_save;
+static struct resource wdt_ich_res[] = {
+ /* TCO */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* SMI */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* GCS */
+ {
+ .flags = IORESOURCE_MEM,
+ },
+};
+
static struct resource gpio_ich_res[] = {
/* BASE */
{
@@ -68,6 +96,11 @@ static struct resource gpio_ich_res[] = {
static struct mfd_cell lpc_ich_cells[] = {
{
+ .name = "iTCO_wdt",
+ .num_resources = ARRAY_SIZE(wdt_ich_res),
+ .resources = wdt_ich_res,
+ },
+ {
.name = "ich_gpio",
.num_resources = ARRAY_SIZE(gpio_ich_res),
.resources = gpio_ich_res,
@@ -196,123 +229,123 @@ enum lpc_chipsets {
};
struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
- {"ICH", 0},
- {"ICH0", 0},
- {"ICH2", 0},
- {"ICH2-M", 0},
- {"ICH3-S", 0},
- {"ICH3-M", 0},
- {"ICH4", 0},
- {"ICH4-M", 0},
- {"C-ICH", 0},
- {"ICH5 or ICH5R", 0},
- {"6300ESB", 0},
- {"ICH6 or ICH6R", 0x0601},
- {"ICH6-M", 0x0601},
- {"ICH6W or ICH6RW", 0x0601},
- {"631xESB/632xESB", 0x0601},
- {"ICH7 or ICH7R", 0x0701},
- {"ICH7DH", 0x0701},
- {"ICH7-M or ICH7-U", 0x0701},
- {"ICH7-M DH", 0x0701},
- {"NM10", 0},
- {"ICH8 or ICH8R", 0x0701},
- {"ICH8DH", 0x0701},
- {"ICH8DO", 0x0701},
- {"ICH8M", 0x0701},
- {"ICH8M-E", 0x0701},
- {"ICH9", 0x0801},
- {"ICH9R", 0x0801},
- {"ICH9DH", 0x0801},
- {"ICH9DO", 0x0801},
- {"ICH9M", 0x0801},
- {"ICH9M-E", 0x0801},
- {"ICH10", 0x0a11},
- {"ICH10R", 0x0a11},
- {"ICH10D", 0x0a01},
- {"ICH10DO", 0x0a01},
- {"PCH Desktop Full Featured", 0x0501},
- {"PCH Mobile Full Featured", 0x0501},
- {"P55", 0x0501},
- {"PM55", 0x0501},
- {"H55", 0x0501},
- {"QM57", 0x0501},
- {"H57", 0x0501},
- {"HM55", 0x0501},
- {"Q57", 0x0501},
- {"HM57", 0x0501},
- {"PCH Mobile SFF Full Featured", 0x0501},
- {"QS57", 0x0501},
- {"3400", 0x0501},
- {"3420", 0x0501},
- {"3450", 0x0501},
- {"EP80579", 0},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Cougar Point", 0x0501},
- {"Patsburg", 0},
- {"Patsburg", 0},
- {"DH89xxCC", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
- {"Panther Point", 0},
+ {"ICH", 1, 0},
+ {"ICH0", 1, 0},
+ {"ICH2", 1, 0},
+ {"ICH2-M", 1, 0},
+ {"ICH3-S", 1, 0},
+ {"ICH3-M", 1, 0},
+ {"ICH4", 1, 0},
+ {"ICH4-M", 1, 0},
+ {"C-ICH", 1, 0},
+ {"ICH5 or ICH5R", 1, 0},
+ {"6300ESB", 1, 0},
+ {"ICH6 or ICH6R", 2, 0x0601},
+ {"ICH6-M", 2, 0x0601},
+ {"ICH6W or ICH6RW", 2, 0x0601},
+ {"631xESB/632xESB", 2, 0x0601},
+ {"ICH7 or ICH7R", 2, 0x0701},
+ {"ICH7DH", 2, 0x0701},
+ {"ICH7-M or ICH7-U", 2, 0x0701},
+ {"ICH7-M DH", 2, 0x0701},
+ {"NM10", 2, 0},
+ {"ICH8 or ICH8R", 2, 0x0701},
+ {"ICH8DH", 2, 0x0701},
+ {"ICH8DO", 2, 0x0701},
+ {"ICH8M", 2, 0x0701},
+ {"ICH8M-E", 2, 0x0701},
+ {"ICH9", 2, 0x0801},
+ {"ICH9R", 2, 0x0801},
+ {"ICH9DH", 2, 0x0801},
+ {"ICH9DO", 2, 0x0801},
+ {"ICH9M", 2, 0x0801},
+ {"ICH9M-E", 2, 0x0801},
+ {"ICH10", 2, 0x0a11},
+ {"ICH10R", 2, 0x0a11},
+ {"ICH10D", 2, 0x0a01},
+ {"ICH10DO", 2, 0x0a01},
+ {"PCH Desktop Full Featured", 2, 0x0501},
+ {"PCH Mobile Full Featured", 2, 0x0501},
+ {"P55", 2, 0x0501},
+ {"PM55", 2, 0x0501},
+ {"H55", 2, 0x0501},
+ {"QM57", 2, 0x0501},
+ {"H57", 2, 0x0501},
+ {"HM55", 2, 0x0501},
+ {"Q57", 2, 0x0501},
+ {"HM57", 2, 0x0501},
+ {"PCH Mobile SFF Full Featured", 2, 0x0501},
+ {"QS57", 2, 0x0501},
+ {"3400", 2, 0x0501},
+ {"3420", 2, 0x0501},
+ {"3450", 2, 0x0501},
+ {"EP80579", 2, 0},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Cougar Point", 2, 0x0501},
+ {"Patsburg", 2, 0},
+ {"Patsburg", 2, 0},
+ {"DH89xxCC", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
+ {"Panther Point", 2, 0},
{NULL, 0}
};
EXPORT_SYMBOL(lpc_chipset_info);
@@ -469,6 +502,12 @@ static int __devinit lpc_ich_probe(struct pci_dev *dev,
return -ENODEV;
}
+ wdt_io_res(ICH_RES_IO_TCO)->start = base_addr + ACPIBASE_TCO_OFF;
+ wdt_io_res(ICH_RES_IO_TCO)->end = base_addr + ACPIBASE_TCO_END;
+
+ wdt_io_res(ICH_RES_IO_SMI)->start = base_addr + ACPIBASE_SMI_OFF;
+ wdt_io_res(ICH_RES_IO_SMI)->end = base_addr + ACPIBASE_SMI_END;
+
gpio_ich_res[ICH_RES_GPE0].start = base_addr + ACPIBASE_GPE_OFF;
gpio_ich_res[ICH_RES_GPE0].end = base_addr + ACPIBASE_GPE_END;
@@ -476,6 +515,25 @@ static int __devinit lpc_ich_probe(struct pci_dev *dev,
pci_read_config_byte(dev, ACPICTRL, &lpc_ich_acpi_save);
pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save | 0x10);
+ /*
+ * Get the Memory-Mapped GCS register. To get access to it
+ * we have to read RCBA from PCI Config space 0xf0 and use
+ * it as base. GCS = RCBA + ICH6_GCS(0x3410).
+ */
+ if (lpc_chipset_info[id->driver_data].iTCO_version == 2) {
+ pci_read_config_dword(dev, RCBABASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0xffffc000;
+ if (base_addr_cfg & 1) {
+ wdt_mem_res(ICH_RES_MEM_GCS)->start = base_addr +
+ ACPIBASE_GCS_OFF;
+ wdt_mem_res(ICH_RES_MEM_GCS)->end = base_addr +
+ ACPIBASE_GCS_END;
+ } else {
+ pr_err("RCBA is disabled by hardware/BIOS, "
+ "device disabled\n");
+ }
+ }
+
/* Setup GPIO base register */
pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
base_addr = base_addr_cfg & 0x0000ff80;
@@ -491,8 +549,11 @@ static int __devinit lpc_ich_probe(struct pci_dev *dev,
pci_read_config_byte(dev, GPIOCTRL, &lpc_ich_gpio_save);
pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save | 0x10);
- for (i=0; i < ARRAY_SIZE(lpc_ich_cells); i++)
+ for (i=0; i < ARRAY_SIZE(lpc_ich_cells); i++) {
lpc_ich_cells[i].id = id->driver_data;
+ lpc_ich_cells[i].platform_data = dev;
+ lpc_ich_cells[i].pdata_size = sizeof(struct pci_dev);
+ }
return mfd_add_devices(&dev->dev, 0,
lpc_ich_cells, ARRAY_SIZE(lpc_ich_cells), NULL, 0);
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 21d816e..8ba1c65 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -545,6 +545,7 @@ config INTEL_SCU_WATCHDOG
config ITCO_WDT
tristate "Intel TCO Timer/Watchdog"
depends on (X86 || IA64) && PCI
+ select LPC_ICH
---help---
Hardware driver for the intel TCO timer based watchdog devices.
These drivers are included in the Intel 82801 I/O Controller
diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h
index 9e27e64..3c57b45 100644
--- a/drivers/watchdog/iTCO_vendor.h
+++ b/drivers/watchdog/iTCO_vendor.h
@@ -1,8 +1,8 @@
/* iTCO Vendor Specific Support hooks */
#ifdef CONFIG_ITCO_VENDOR_SUPPORT
-extern void iTCO_vendor_pre_start(unsigned long, unsigned int);
-extern void iTCO_vendor_pre_stop(unsigned long);
-extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int);
+extern void iTCO_vendor_pre_start(struct resource *, unsigned int);
+extern void iTCO_vendor_pre_stop(struct resource *);
+extern void iTCO_vendor_pre_keepalive(struct resource *, unsigned int);
extern void iTCO_vendor_pre_set_heartbeat(unsigned int);
extern int iTCO_vendor_check_noreboot_on(void);
#else
diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c
index 481d1ad..3b80d6f 100644
--- a/drivers/watchdog/iTCO_vendor_support.c
+++ b/drivers/watchdog/iTCO_vendor_support.c
@@ -34,11 +34,6 @@
#include "iTCO_vendor.h"
-/* iTCO defines */
-#define SMI_EN (acpibase + 0x30) /* SMI Control and Enable Register */
-#define TCOBASE (acpibase + 0x60) /* TCO base address */
-#define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */
-
/* List of vendor support modes */
/* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */
#define SUPERMICRO_OLD_BOARD 1
@@ -81,24 +76,24 @@ MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default="
* 20.6 seconds.
*/
-static void supermicro_old_pre_start(unsigned long acpibase)
+static void supermicro_old_pre_start(struct resource *smires)
{
unsigned long val32;
/* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
- outl(val32, SMI_EN); /* Needed to activate watchdog */
+ outl(val32, smires->start); /* Needed to activate watchdog */
}
-static void supermicro_old_pre_stop(unsigned long acpibase)
+static void supermicro_old_pre_stop(struct resource *smires)
{
unsigned long val32;
/* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
val32 |= 0x00002000; /* Turn on SMI clearing watchdog */
- outl(val32, SMI_EN); /* Needed to deactivate watchdog */
+ outl(val32, smires->start); /* Needed to deactivate watchdog */
}
/*
@@ -269,66 +264,66 @@ static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat)
* Don't use this fix if you don't need to!!!
*/
-static void broken_bios_start(unsigned long acpibase)
+static void broken_bios_start(struct resource *smires)
{
unsigned long val32;
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
/* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI#
Bit 0: GBL_SMI_EN -> 0 = No SMI# will be generated by ICH. */
val32 &= 0xffffdffe;
- outl(val32, SMI_EN);
+ outl(val32, smires->start);
}
-static void broken_bios_stop(unsigned long acpibase)
+static void broken_bios_stop(struct resource *smires)
{
unsigned long val32;
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
/* Bit 13: TCO_EN -> 1 = Enables TCO logic generating an SMI#
Bit 0: GBL_SMI_EN -> 1 = Turn global SMI on again. */
val32 |= 0x00002001;
- outl(val32, SMI_EN);
+ outl(val32, smires->start);
}
/*
* Generic Support Functions
*/
-void iTCO_vendor_pre_start(unsigned long acpibase,
+void iTCO_vendor_pre_start(struct resource *smires,
unsigned int heartbeat)
{
switch (vendorsupport) {
case SUPERMICRO_OLD_BOARD:
- supermicro_old_pre_start(acpibase);
+ supermicro_old_pre_start(smires);
break;
case SUPERMICRO_NEW_BOARD:
supermicro_new_pre_start(heartbeat);
break;
case BROKEN_BIOS:
- broken_bios_start(acpibase);
+ broken_bios_start(smires);
break;
}
}
EXPORT_SYMBOL(iTCO_vendor_pre_start);
-void iTCO_vendor_pre_stop(unsigned long acpibase)
+void iTCO_vendor_pre_stop(struct resource *smires)
{
switch (vendorsupport) {
case SUPERMICRO_OLD_BOARD:
- supermicro_old_pre_stop(acpibase);
+ supermicro_old_pre_stop(smires);
break;
case SUPERMICRO_NEW_BOARD:
supermicro_new_pre_stop();
break;
case BROKEN_BIOS:
- broken_bios_stop(acpibase);
+ broken_bios_stop(smires);
break;
}
}
EXPORT_SYMBOL(iTCO_vendor_pre_stop);
-void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat)
+void iTCO_vendor_pre_keepalive(struct resource *smires, unsigned int heartbeat)
{
if (vendorsupport == SUPERMICRO_NEW_BOARD)
supermicro_new_pre_set_heartbeat(heartbeat);
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
index 5fd020d..86a790c 100644
--- a/drivers/watchdog/iTCO_wdt.c
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -44,8 +44,8 @@
/* Module and version information */
#define DRV_NAME "iTCO_wdt"
-#define DRV_VERSION "1.06"
-#define PFX DRV_NAME ": "
+#define DRV_VERSION "1.07"
+#define pr_fmt(fmt) DRV_NAME ": " fmt
/* Includes */
#include <linux/module.h> /* For module specific items */
@@ -64,254 +64,11 @@
#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <linux/io.h> /* For inb/outb/... */
+#include <linux/mfd/core.h>
+#include <linux/mfd/lpc_ich.h>
#include "iTCO_vendor.h"
-/* TCO related info */
-enum iTCO_chipsets {
- TCO_ICH = 0, /* ICH */
- TCO_ICH0, /* ICH0 */
- TCO_ICH2, /* ICH2 */
- TCO_ICH2M, /* ICH2-M */
- TCO_ICH3, /* ICH3-S */
- TCO_ICH3M, /* ICH3-M */
- TCO_ICH4, /* ICH4 */
- TCO_ICH4M, /* ICH4-M */
- TCO_CICH, /* C-ICH */
- TCO_ICH5, /* ICH5 & ICH5R */
- TCO_6300ESB, /* 6300ESB */
- TCO_ICH6, /* ICH6 & ICH6R */
- TCO_ICH6M, /* ICH6-M */
- TCO_ICH6W, /* ICH6W & ICH6RW */
- TCO_631XESB, /* 631xESB/632xESB */
- TCO_ICH7, /* ICH7 & ICH7R */
- TCO_ICH7DH, /* ICH7DH */
- TCO_ICH7M, /* ICH7-M & ICH7-U */
- TCO_ICH7MDH, /* ICH7-M DH */
- TCO_NM10, /* NM10 */
- TCO_ICH8, /* ICH8 & ICH8R */
- TCO_ICH8DH, /* ICH8DH */
- TCO_ICH8DO, /* ICH8DO */
- TCO_ICH8M, /* ICH8M */
- TCO_ICH8ME, /* ICH8M-E */
- TCO_ICH9, /* ICH9 */
- TCO_ICH9R, /* ICH9R */
- TCO_ICH9DH, /* ICH9DH */
- TCO_ICH9DO, /* ICH9DO */
- TCO_ICH9M, /* ICH9M */
- TCO_ICH9ME, /* ICH9M-E */
- TCO_ICH10, /* ICH10 */
- TCO_ICH10R, /* ICH10R */
- TCO_ICH10D, /* ICH10D */
- TCO_ICH10DO, /* ICH10DO */
- TCO_PCH, /* PCH Desktop Full Featured */
- TCO_PCHM, /* PCH Mobile Full Featured */
- TCO_P55, /* P55 */
- TCO_PM55, /* PM55 */
- TCO_H55, /* H55 */
- TCO_QM57, /* QM57 */
- TCO_H57, /* H57 */
- TCO_HM55, /* HM55 */
- TCO_Q57, /* Q57 */
- TCO_HM57, /* HM57 */
- TCO_PCHMSFF, /* PCH Mobile SFF Full Featured */
- TCO_QS57, /* QS57 */
- TCO_3400, /* 3400 */
- TCO_3420, /* 3420 */
- TCO_3450, /* 3450 */
- TCO_EP80579, /* EP80579 */
- TCO_CPT1, /* Cougar Point */
- TCO_CPT2, /* Cougar Point Desktop */
- TCO_CPT3, /* Cougar Point Mobile */
- TCO_CPT4, /* Cougar Point */
- TCO_CPT5, /* Cougar Point */
- TCO_CPT6, /* Cougar Point */
- TCO_CPT7, /* Cougar Point */
- TCO_CPT8, /* Cougar Point */
- TCO_CPT9, /* Cougar Point */
- TCO_CPT10, /* Cougar Point */
- TCO_CPT11, /* Cougar Point */
- TCO_CPT12, /* Cougar Point */
- TCO_CPT13, /* Cougar Point */
- TCO_CPT14, /* Cougar Point */
- TCO_CPT15, /* Cougar Point */
- TCO_CPT16, /* Cougar Point */
- TCO_CPT17, /* Cougar Point */
- TCO_CPT18, /* Cougar Point */
- TCO_CPT19, /* Cougar Point */
- TCO_CPT20, /* Cougar Point */
- TCO_CPT21, /* Cougar Point */
- TCO_CPT22, /* Cougar Point */
- TCO_CPT23, /* Cougar Point */
- TCO_CPT24, /* Cougar Point */
- TCO_CPT25, /* Cougar Point */
- TCO_CPT26, /* Cougar Point */
- TCO_CPT27, /* Cougar Point */
- TCO_CPT28, /* Cougar Point */
- TCO_CPT29, /* Cougar Point */
- TCO_CPT30, /* Cougar Point */
- TCO_CPT31, /* Cougar Point */
- TCO_PBG1, /* Patsburg */
- TCO_PBG2, /* Patsburg */
- TCO_DH89XXCC, /* DH89xxCC */
- TCO_PPT0, /* Panther Point */
- TCO_PPT1, /* Panther Point */
- TCO_PPT2, /* Panther Point */
- TCO_PPT3, /* Panther Point */
- TCO_PPT4, /* Panther Point */
- TCO_PPT5, /* Panther Point */
- TCO_PPT6, /* Panther Point */
- TCO_PPT7, /* Panther Point */
- TCO_PPT8, /* Panther Point */
- TCO_PPT9, /* Panther Point */
- TCO_PPT10, /* Panther Point */
- TCO_PPT11, /* Panther Point */
- TCO_PPT12, /* Panther Point */
- TCO_PPT13, /* Panther Point */
- TCO_PPT14, /* Panther Point */
- TCO_PPT15, /* Panther Point */
- TCO_PPT16, /* Panther Point */
- TCO_PPT17, /* Panther Point */
- TCO_PPT18, /* Panther Point */
- TCO_PPT19, /* Panther Point */
- TCO_PPT20, /* Panther Point */
- TCO_PPT21, /* Panther Point */
- TCO_PPT22, /* Panther Point */
- TCO_PPT23, /* Panther Point */
- TCO_PPT24, /* Panther Point */
- TCO_PPT25, /* Panther Point */
- TCO_PPT26, /* Panther Point */
- TCO_PPT27, /* Panther Point */
- TCO_PPT28, /* Panther Point */
- TCO_PPT29, /* Panther Point */
- TCO_PPT30, /* Panther Point */
- TCO_PPT31, /* Panther Point */
-};
-
-static struct {
- char *name;
- unsigned int iTCO_version;
-} iTCO_chipset_info[] __devinitdata = {
- {"ICH", 1},
- {"ICH0", 1},
- {"ICH2", 1},
- {"ICH2-M", 1},
- {"ICH3-S", 1},
- {"ICH3-M", 1},
- {"ICH4", 1},
- {"ICH4-M", 1},
- {"C-ICH", 1},
- {"ICH5 or ICH5R", 1},
- {"6300ESB", 1},
- {"ICH6 or ICH6R", 2},
- {"ICH6-M", 2},
- {"ICH6W or ICH6RW", 2},
- {"631xESB/632xESB", 2},
- {"ICH7 or ICH7R", 2},
- {"ICH7DH", 2},
- {"ICH7-M or ICH7-U", 2},
- {"ICH7-M DH", 2},
- {"NM10", 2},
- {"ICH8 or ICH8R", 2},
- {"ICH8DH", 2},
- {"ICH8DO", 2},
- {"ICH8M", 2},
- {"ICH8M-E", 2},
- {"ICH9", 2},
- {"ICH9R", 2},
- {"ICH9DH", 2},
- {"ICH9DO", 2},
- {"ICH9M", 2},
- {"ICH9M-E", 2},
- {"ICH10", 2},
- {"ICH10R", 2},
- {"ICH10D", 2},
- {"ICH10DO", 2},
- {"PCH Desktop Full Featured", 2},
- {"PCH Mobile Full Featured", 2},
- {"P55", 2},
- {"PM55", 2},
- {"H55", 2},
- {"QM57", 2},
- {"H57", 2},
- {"HM55", 2},
- {"Q57", 2},
- {"HM57", 2},
- {"PCH Mobile SFF Full Featured", 2},
- {"QS57", 2},
- {"3400", 2},
- {"3420", 2},
- {"3450", 2},
- {"EP80579", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Cougar Point", 2},
- {"Patsburg", 2},
- {"Patsburg", 2},
- {"DH89xxCC", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {"Panther Point", 2},
- {NULL, 0}
-};
-
#define ITCO_PCI_DEVICE(dev, data) \
.vendor = PCI_VENDOR_ID_INTEL, \
.device = dev, \
@@ -321,154 +78,11 @@ static struct {
.class_mask = 0, \
.driver_data = data
-/*
- * This data only exists for exporting the supported PCI ids
- * via MODULE_DEVICE_TABLE. We do not actually register a
- * pci_driver, because the I/O Controller Hub has also other
- * functions that probably will be registered by other drivers.
- */
-static DEFINE_PCI_DEVICE_TABLE(iTCO_wdt_pci_tbl) = {
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801AA_0, TCO_ICH)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801AB_0, TCO_ICH0)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801BA_0, TCO_ICH2)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801BA_10, TCO_ICH2M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801CA_0, TCO_ICH3)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801CA_12, TCO_ICH3M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801DB_0, TCO_ICH4)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801DB_12, TCO_ICH4M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801E_0, TCO_CICH)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801EB_0, TCO_ICH5)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ESB_1, TCO_6300ESB)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_0, TCO_ICH6)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_1, TCO_ICH6M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_2, TCO_ICH6W)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ESB2_0, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2671, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2672, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2673, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2674, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2675, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2676, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2677, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2678, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x2679, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267a, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267b, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267c, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267d, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267e, TCO_631XESB)},
- { ITCO_PCI_DEVICE(0x267f, TCO_631XESB)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_0, TCO_ICH7)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_30, TCO_ICH7DH)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_1, TCO_ICH7M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_31, TCO_ICH7MDH)},
- { ITCO_PCI_DEVICE(0x27bc, TCO_NM10)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_0, TCO_ICH8)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_2, TCO_ICH8DH)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_3, TCO_ICH8DO)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_4, TCO_ICH8M)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_1, TCO_ICH8ME)},
- { ITCO_PCI_DEVICE(0x2918, TCO_ICH9)},
- { ITCO_PCI_DEVICE(0x2916, TCO_ICH9R)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH9_2, TCO_ICH9DH)},
- { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH9_4, TCO_ICH9DO)},
- { ITCO_PCI_DEVICE(0x2919, TCO_ICH9M)},
- { ITCO_PCI_DEVICE(0x2917, TCO_ICH9ME)},
- { ITCO_PCI_DEVICE(0x3a18, TCO_ICH10)},
- { ITCO_PCI_DEVICE(0x3a16, TCO_ICH10R)},
- { ITCO_PCI_DEVICE(0x3a1a, TCO_ICH10D)},
- { ITCO_PCI_DEVICE(0x3a14, TCO_ICH10DO)},
- { ITCO_PCI_DEVICE(0x3b00, TCO_PCH)},
- { ITCO_PCI_DEVICE(0x3b01, TCO_PCHM)},
- { ITCO_PCI_DEVICE(0x3b02, TCO_P55)},
- { ITCO_PCI_DEVICE(0x3b03, TCO_PM55)},
- { ITCO_PCI_DEVICE(0x3b06, TCO_H55)},
- { ITCO_PCI_DEVICE(0x3b07, TCO_QM57)},
- { ITCO_PCI_DEVICE(0x3b08, TCO_H57)},
- { ITCO_PCI_DEVICE(0x3b09, TCO_HM55)},
- { ITCO_PCI_DEVICE(0x3b0a, TCO_Q57)},
- { ITCO_PCI_DEVICE(0x3b0b, TCO_HM57)},
- { ITCO_PCI_DEVICE(0x3b0d, TCO_PCHMSFF)},
- { ITCO_PCI_DEVICE(0x3b0f, TCO_QS57)},
- { ITCO_PCI_DEVICE(0x3b12, TCO_3400)},
- { ITCO_PCI_DEVICE(0x3b14, TCO_3420)},
- { ITCO_PCI_DEVICE(0x3b16, TCO_3450)},
- { ITCO_PCI_DEVICE(0x5031, TCO_EP80579)},
- { ITCO_PCI_DEVICE(0x1c41, TCO_CPT1)},
- { ITCO_PCI_DEVICE(0x1c42, TCO_CPT2)},
- { ITCO_PCI_DEVICE(0x1c43, TCO_CPT3)},
- { ITCO_PCI_DEVICE(0x1c44, TCO_CPT4)},
- { ITCO_PCI_DEVICE(0x1c45, TCO_CPT5)},
- { ITCO_PCI_DEVICE(0x1c46, TCO_CPT6)},
- { ITCO_PCI_DEVICE(0x1c47, TCO_CPT7)},
- { ITCO_PCI_DEVICE(0x1c48, TCO_CPT8)},
- { ITCO_PCI_DEVICE(0x1c49, TCO_CPT9)},
- { ITCO_PCI_DEVICE(0x1c4a, TCO_CPT10)},
- { ITCO_PCI_DEVICE(0x1c4b, TCO_CPT11)},
- { ITCO_PCI_DEVICE(0x1c4c, TCO_CPT12)},
- { ITCO_PCI_DEVICE(0x1c4d, TCO_CPT13)},
- { ITCO_PCI_DEVICE(0x1c4e, TCO_CPT14)},
- { ITCO_PCI_DEVICE(0x1c4f, TCO_CPT15)},
- { ITCO_PCI_DEVICE(0x1c50, TCO_CPT16)},
- { ITCO_PCI_DEVICE(0x1c51, TCO_CPT17)},
- { ITCO_PCI_DEVICE(0x1c52, TCO_CPT18)},
- { ITCO_PCI_DEVICE(0x1c53, TCO_CPT19)},
- { ITCO_PCI_DEVICE(0x1c54, TCO_CPT20)},
- { ITCO_PCI_DEVICE(0x1c55, TCO_CPT21)},
- { ITCO_PCI_DEVICE(0x1c56, TCO_CPT22)},
- { ITCO_PCI_DEVICE(0x1c57, TCO_CPT23)},
- { ITCO_PCI_DEVICE(0x1c58, TCO_CPT24)},
- { ITCO_PCI_DEVICE(0x1c59, TCO_CPT25)},
- { ITCO_PCI_DEVICE(0x1c5a, TCO_CPT26)},
- { ITCO_PCI_DEVICE(0x1c5b, TCO_CPT27)},
- { ITCO_PCI_DEVICE(0x1c5c, TCO_CPT28)},
- { ITCO_PCI_DEVICE(0x1c5d, TCO_CPT29)},
- { ITCO_PCI_DEVICE(0x1c5e, TCO_CPT30)},
- { ITCO_PCI_DEVICE(0x1c5f, TCO_CPT31)},
- { ITCO_PCI_DEVICE(0x1d40, TCO_PBG1)},
- { ITCO_PCI_DEVICE(0x1d41, TCO_PBG2)},
- { ITCO_PCI_DEVICE(0x2310, TCO_DH89XXCC)},
- { ITCO_PCI_DEVICE(0x1e40, TCO_PPT0)},
- { ITCO_PCI_DEVICE(0x1e41, TCO_PPT1)},
- { ITCO_PCI_DEVICE(0x1e42, TCO_PPT2)},
- { ITCO_PCI_DEVICE(0x1e43, TCO_PPT3)},
- { ITCO_PCI_DEVICE(0x1e44, TCO_PPT4)},
- { ITCO_PCI_DEVICE(0x1e45, TCO_PPT5)},
- { ITCO_PCI_DEVICE(0x1e46, TCO_PPT6)},
- { ITCO_PCI_DEVICE(0x1e47, TCO_PPT7)},
- { ITCO_PCI_DEVICE(0x1e48, TCO_PPT8)},
- { ITCO_PCI_DEVICE(0x1e49, TCO_PPT9)},
- { ITCO_PCI_DEVICE(0x1e4a, TCO_PPT10)},
- { ITCO_PCI_DEVICE(0x1e4b, TCO_PPT11)},
- { ITCO_PCI_DEVICE(0x1e4c, TCO_PPT12)},
- { ITCO_PCI_DEVICE(0x1e4d, TCO_PPT13)},
- { ITCO_PCI_DEVICE(0x1e4e, TCO_PPT14)},
- { ITCO_PCI_DEVICE(0x1e4f, TCO_PPT15)},
- { ITCO_PCI_DEVICE(0x1e50, TCO_PPT16)},
- { ITCO_PCI_DEVICE(0x1e51, TCO_PPT17)},
- { ITCO_PCI_DEVICE(0x1e52, TCO_PPT18)},
- { ITCO_PCI_DEVICE(0x1e53, TCO_PPT19)},
- { ITCO_PCI_DEVICE(0x1e54, TCO_PPT20)},
- { ITCO_PCI_DEVICE(0x1e55, TCO_PPT21)},
- { ITCO_PCI_DEVICE(0x1e56, TCO_PPT22)},
- { ITCO_PCI_DEVICE(0x1e57, TCO_PPT23)},
- { ITCO_PCI_DEVICE(0x1e58, TCO_PPT24)},
- { ITCO_PCI_DEVICE(0x1e59, TCO_PPT25)},
- { ITCO_PCI_DEVICE(0x1e5a, TCO_PPT26)},
- { ITCO_PCI_DEVICE(0x1e5b, TCO_PPT27)},
- { ITCO_PCI_DEVICE(0x1e5c, TCO_PPT28)},
- { ITCO_PCI_DEVICE(0x1e5d, TCO_PPT29)},
- { ITCO_PCI_DEVICE(0x1e5e, TCO_PPT30)},
- { ITCO_PCI_DEVICE(0x1e5f, TCO_PPT31)},
- { 0, }, /* End of list */
-};
-MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl);
-
/* Address definitions for the TCO */
/* TCO base address */
-#define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60)
+#define TCOBASE iTCO_wdt_private.tco_res->start
/* SMI Control and Enable Register */
-#define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30)
+#define SMI_EN iTCO_wdt_private.smi_res->start
#define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */
#define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */
@@ -486,19 +100,18 @@ static char expect_release;
static struct { /* this is private data for the iTCO_wdt device */
/* TCO version/generation */
unsigned int iTCO_version;
- /* The device's ACPIBASE address (TCOBASE = ACPIBASE+0x60) */
- unsigned long ACPIBASE;
+ struct resource *tco_res;
+ struct resource *smi_res;
+ struct resource *gcs_res;
/* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/
unsigned long __iomem *gcs;
/* the lock for io operations */
spinlock_t io_lock;
+ struct platform_device *dev;
/* the PCI-device */
struct pci_dev *pdev;
} iTCO_wdt_private;
-/* the watchdog platform device */
-static struct platform_device *iTCO_wdt_platform_device;
-
/* module parameters */
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
@@ -573,12 +186,12 @@ static int iTCO_wdt_start(void)
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat);
+ iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, heartbeat);
/* disable chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit()) {
spin_unlock(&iTCO_wdt_private.io_lock);
- printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, "
+ pr_err("failed to reset NO_REBOOT flag, "
"reboot disabled by hardware/BIOS\n");
return -EIO;
}
@@ -608,7 +221,7 @@ static int iTCO_wdt_stop(void)
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE);
+ iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res);
/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
val = inw(TCO1_CNT);
@@ -630,7 +243,7 @@ static int iTCO_wdt_keepalive(void)
{
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat);
+ iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, heartbeat);
/* Reload the timer by writing to the TCO Timer Counter register */
if (iTCO_wdt_private.iTCO_version == 2)
@@ -750,8 +363,7 @@ static int iTCO_wdt_release(struct inode *inode, struct file *file)
if (expect_release == 42) {
iTCO_wdt_stop();
} else {
- printk(KERN_CRIT PFX
- "Unexpected close, not stopping watchdog!\n");
+ pr_crit("Unexpected close, not stopping watchdog!\n");
iTCO_wdt_keepalive();
}
clear_bit(0, &is_active);
@@ -876,51 +488,67 @@ static struct miscdevice iTCO_wdt_miscdev = {
* Init & exit routines
*/
-static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
- const struct pci_device_id *ent, struct platform_device *dev)
+static void __devexit iTCO_wdt_cleanup(void)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ iTCO_wdt_stop();
+
+ /* Deregister */
+ misc_deregister(&iTCO_wdt_miscdev);
+ release_resource(iTCO_wdt_private.tco_res);
+ release_resource(iTCO_wdt_private.smi_res);
+ release_resource(iTCO_wdt_private.gcs_res);
+ if (iTCO_wdt_private.iTCO_version == 2)
+ iounmap(iTCO_wdt_private.gcs);
+ iTCO_wdt_private.tco_res = NULL;
+ iTCO_wdt_private.smi_res = NULL;
+ iTCO_wdt_private.gcs_res = NULL;
+}
+
+static int __devinit iTCO_wdt_probe(struct platform_device *dev)
{
- int ret;
- u32 base_address;
- unsigned long RCBA;
+ int ret = -ENODEV;
unsigned long val32;
- /*
- * Find the ACPI/PM base I/O address which is the base
- * for the TCO registers (TCOBASE=ACPIBASE + 0x60)
- * ACPIBASE is bits [15:7] from 0x40-0x43
- */
- pci_read_config_dword(pdev, 0x40, &base_address);
- base_address &= 0x0000ff80;
- if (base_address == 0x00000000) {
- /* Something's wrong here, ACPIBASE has to be set */
- printk(KERN_ERR PFX "failed to get TCOBASE address, "
- "device disabled by hardware/BIOS\n");
- return -ENODEV;
+ spin_lock_init(&iTCO_wdt_private.io_lock);
+
+ iTCO_wdt_private.tco_res =
+ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO);
+
+ iTCO_wdt_private.smi_res =
+ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI);
+
+ iTCO_wdt_private.gcs_res =
+ platform_get_resource(dev, IORESOURCE_MEM, ICH_RES_MEM_GCS);
+
+ if (!iTCO_wdt_private.tco_res || !iTCO_wdt_private.smi_res ||
+ !iTCO_wdt_private.gcs_res) {
+ pr_info("No device detected.\n");
+ return ret;
}
+
iTCO_wdt_private.iTCO_version =
- iTCO_chipset_info[ent->driver_data].iTCO_version;
- iTCO_wdt_private.ACPIBASE = base_address;
- iTCO_wdt_private.pdev = pdev;
-
- /* Get the Memory-Mapped GCS register, we need it for the
- NO_REBOOT flag (TCO v2). To get access to it you have to
- read RCBA from PCI Config space 0xf0 and use it as base.
- GCS = RCBA + ICH6_GCS(0x3410). */
+ lpc_chipset_info[dev->id].iTCO_version;
+ iTCO_wdt_private.dev = dev;
+ iTCO_wdt_private.pdev = dev->mfd_cell->platform_data;
+
+ /*
+ * Get the Memory-Mapped GCS register, we need it for the
+ * NO_REBOOT flag (TCO v2).
+ */
if (iTCO_wdt_private.iTCO_version == 2) {
- pci_read_config_dword(pdev, 0xf0, &base_address);
- if ((base_address & 1) == 0) {
- printk(KERN_ERR PFX "RCBA is disabled by hardware"
- "/BIOS, device disabled\n");
- ret = -ENODEV;
+ if (!request_mem_region(iTCO_wdt_private.gcs_res->start,
+ resource_size(iTCO_wdt_private.gcs_res), dev->name)) {
goto out;
}
- RCBA = base_address & 0xffffc000;
- iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4);
+ iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start,
+ resource_size(iTCO_wdt_private.gcs_res));
}
/* Check chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) {
- printk(KERN_INFO PFX "unable to reset NO_REBOOT flag, "
+ pr_info("unable to reset NO_REBOOT flag, "
"device disabled by hardware/BIOS\n");
ret = -ENODEV; /* Cannot reset NO_REBOOT bit */
goto out_unmap;
@@ -930,9 +558,9 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
iTCO_wdt_set_NO_REBOOT_bit();
/* The TCO logic uses the TCO_EN bit in the SMI_EN register */
- if (!request_region(SMI_EN, 4, "iTCO_wdt")) {
- printk(KERN_ERR PFX
- "I/O address 0x%04lx already in use, "
+ if (!request_region(iTCO_wdt_private.smi_res->start,
+ resource_size(iTCO_wdt_private.smi_res), dev->name)) {
+ pr_err("I/O address 0x%04llx already in use, "
"device disabled\n", SMI_EN);
ret = -EIO;
goto out_unmap;
@@ -942,19 +570,17 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
outl(val32, SMI_EN);
- /* The TCO I/O registers reside in a 32-byte range pointed to
- by the TCOBASE value */
- if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) {
- printk(KERN_ERR PFX "I/O address 0x%04lx already in use "
- "device disabled\n", TCOBASE);
+ if (!request_region(iTCO_wdt_private.tco_res->start,
+ resource_size(iTCO_wdt_private.tco_res), dev->name)) {
+ pr_err("I/O address 0x%04llx already in use device disabled\n",
+ TCOBASE);
ret = -EIO;
goto unreg_smi_en;
}
- printk(KERN_INFO PFX
- "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n",
- iTCO_chipset_info[ent->driver_data].name,
- iTCO_chipset_info[ent->driver_data].iTCO_version,
+ pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n",
+ lpc_chipset_info[dev->id].name,
+ lpc_chipset_info[dev->id].iTCO_version,
TCOBASE);
/* Clear out the (probably old) status */
@@ -969,79 +595,38 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
if not reset to the default */
if (iTCO_wdt_set_heartbeat(heartbeat)) {
iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT);
- printk(KERN_INFO PFX
- "timeout value out of range, using %d\n", heartbeat);
+ pr_info("timeout value out of range, using %d\n", heartbeat);
}
ret = misc_register(&iTCO_wdt_miscdev);
if (ret != 0) {
- printk(KERN_ERR PFX
- "cannot register miscdev on minor=%d (err=%d)\n",
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_region;
}
- printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
heartbeat, nowayout);
return 0;
unreg_region:
- release_region(TCOBASE, 0x20);
+ release_resource(iTCO_wdt_private.tco_res);
unreg_smi_en:
- release_region(SMI_EN, 4);
+ release_resource(iTCO_wdt_private.tco_res);
out_unmap:
if (iTCO_wdt_private.iTCO_version == 2)
iounmap(iTCO_wdt_private.gcs);
out:
- iTCO_wdt_private.ACPIBASE = 0;
- return ret;
-}
-
-static void __devexit iTCO_wdt_cleanup(void)
-{
- /* Stop the timer before we leave */
- if (!nowayout)
- iTCO_wdt_stop();
-
- /* Deregister */
- misc_deregister(&iTCO_wdt_miscdev);
- release_region(TCOBASE, 0x20);
- release_region(SMI_EN, 4);
- if (iTCO_wdt_private.iTCO_version == 2)
- iounmap(iTCO_wdt_private.gcs);
- pci_dev_put(iTCO_wdt_private.pdev);
- iTCO_wdt_private.ACPIBASE = 0;
-}
-
-static int __devinit iTCO_wdt_probe(struct platform_device *dev)
-{
- int ret = -ENODEV;
- int found = 0;
- struct pci_dev *pdev = NULL;
- const struct pci_device_id *ent;
-
- spin_lock_init(&iTCO_wdt_private.io_lock);
-
- for_each_pci_dev(pdev) {
- ent = pci_match_id(iTCO_wdt_pci_tbl, pdev);
- if (ent) {
- found++;
- ret = iTCO_wdt_init(pdev, ent, dev);
- if (!ret)
- break;
- }
- }
-
- if (!found)
- printk(KERN_INFO PFX "No device detected.\n");
+ iTCO_wdt_private.tco_res = NULL;
+ iTCO_wdt_private.smi_res = NULL;
return ret;
}
static int __devexit iTCO_wdt_remove(struct platform_device *dev)
{
- if (iTCO_wdt_private.ACPIBASE)
+ if (iTCO_wdt_private.tco_res || iTCO_wdt_private.smi_res)
iTCO_wdt_cleanup();
return 0;
@@ -1071,32 +656,19 @@ static int __init iTCO_wdt_init_module(void)
{
int err;
- printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s\n",
- DRV_VERSION);
+ pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION);
err = platform_driver_register(&iTCO_wdt_driver);
if (err)
return err;
- iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME,
- -1, NULL, 0);
- if (IS_ERR(iTCO_wdt_platform_device)) {
- err = PTR_ERR(iTCO_wdt_platform_device);
- goto unreg_platform_driver;
- }
-
return 0;
-
-unreg_platform_driver:
- platform_driver_unregister(&iTCO_wdt_driver);
- return err;
}
static void __exit iTCO_wdt_cleanup_module(void)
{
- platform_device_unregister(iTCO_wdt_platform_device);
platform_driver_unregister(&iTCO_wdt_driver);
- printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
+ pr_info("Watchdog Module Unloaded.\n");
}
module_init(iTCO_wdt_init_module);
diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
index 27a1c32..b6a3e82 100644
--- a/include/linux/mfd/lpc_ich.h
+++ b/include/linux/mfd/lpc_ich.h
@@ -20,12 +20,19 @@
#ifndef LPC_ICH_H
#define LPC_ICH_H
+/* Watchdog resources */
+#define ICH_RES_IO_TCO 0
+#define ICH_RES_IO_SMI 1
+#define ICH_RES_MEM_OFF 2
+#define ICH_RES_MEM_GCS 0
+
/* GPIO resources */
#define ICH_RES_GPIO 0
#define ICH_RES_GPE0 1
struct lpc_ich_info {
char *name;
+ unsigned int iTCO_version;
unsigned int gpio_version;
};
--
1.7.0.4
This driver currently creates resources for use by a forthcoming ICH
chipset GPIO driver. It could be expanded to created the resources for
converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
drivers to use the mfd model.
Signed-off-by: Aaron Sierra <[email protected]>
---
drivers/mfd/Kconfig | 9 +
drivers/mfd/Makefile | 1 +
drivers/mfd/lpc_ich.c | 531 +++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/lpc_ich.h | 33 +++
4 files changed, 574 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/lpc_ich.c
create mode 100644 include/linux/mfd/lpc_ich.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6ca938a..18eca82 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -636,6 +636,15 @@ config LPC_SCH
LPC bridge function of the Intel SCH provides support for
System Management Bus and General Purpose I/O.
+config LPC_ICH
+ tristate "Intel ICH LPC"
+ depends on PCI
+ select MFD_CORE
+ help
+ The LPC bridge function of the Intel ICH provides support for
+ many functional units. This driver provides needed support for
+ other drivers to control these functions, currently GPIO.
+
config MFD_RDC321X
tristate "Support for RDC-R321x southbridge"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d7d47d2..d479882 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
+obj-$(CONFIG_LPC_ICH) += lpc_ich.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
new file mode 100644
index 0000000..a288c74
--- /dev/null
+++ b/drivers/mfd/lpc_ich.c
@@ -0,0 +1,531 @@
+/*
+ * lpc_ich.c - LPC interface for Intel ICH
+ *
+ * LPC bridge function of the Intel ICH contains many other
+ * functional units, such as Interrupt controllers, Timers,
+ * Power Management, System Management, GPIO, RTC, and LPC
+ * Configuration Registers.
+ *
+ * This driver is derived from lpc_sch.
+
+ * Copyright (c) 2011 Extreme Engineering Solution, Inc.
+ * Author: Aaron Sierra <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/lpc_ich.h>
+
+/* Convenient wrapper to make our PCI ID table */
+#define ICHX_PDEV(dev, idx) \
+ .vendor = PCI_VENDOR_ID_INTEL, \
+ .device = dev, \
+ .subvendor = PCI_ANY_ID, \
+ .subdevice = PCI_ANY_ID, \
+ .class = 0, \
+ .class_mask = 0, \
+ .driver_data = idx
+
+#define ACPIBASE 0x40
+#define ACPIBASE_GPE_OFF 0x20
+#define ACPIBASE_GPE_END 0x2f
+#define ACPICTRL 0x44
+
+#define GPIOBASE 0x48
+#define GPIOCTRL 0x4C
+#define GPIOBASE_IO_SIZE 0x80
+
+static u8 lpc_ich_acpi_save, lpc_ich_gpio_save;
+
+static struct resource gpio_ich_res[] = {
+ /* BASE */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* ACPI */
+ {
+ .flags = IORESOURCE_IO,
+ },
+};
+
+static struct mfd_cell lpc_ich_cells[] = {
+ {
+ .name = "ich_gpio",
+ .num_resources = ARRAY_SIZE(gpio_ich_res),
+ .resources = gpio_ich_res,
+ },
+};
+
+/* TCO related info */
+enum lpc_chipsets {
+ LPC_ICH = 0, /* ICH */
+ LPC_ICH0, /* ICH0 */
+ LPC_ICH2, /* ICH2 */
+ LPC_ICH2M, /* ICH2-M */
+ LPC_ICH3, /* ICH3-S */
+ LPC_ICH3M, /* ICH3-M */
+ LPC_ICH4, /* ICH4 */
+ LPC_ICH4M, /* ICH4-M */
+ LPC_CICH, /* C-ICH */
+ LPC_ICH5, /* ICH5 & ICH5R */
+ LPC_6300ESB, /* 6300ESB */
+ LPC_ICH6, /* ICH6 & ICH6R */
+ LPC_ICH6M, /* ICH6-M */
+ LPC_ICH6W, /* ICH6W & ICH6RW */
+ LPC_631XESB, /* 631xESB/632xESB */
+ LPC_ICH7, /* ICH7 & ICH7R */
+ LPC_ICH7DH, /* ICH7DH */
+ LPC_ICH7M, /* ICH7-M & ICH7-U */
+ LPC_ICH7MDH, /* ICH7-M DH */
+ LPC_NM10, /* NM10 */
+ LPC_ICH8, /* ICH8 & ICH8R */
+ LPC_ICH8DH, /* ICH8DH */
+ LPC_ICH8DO, /* ICH8DO */
+ LPC_ICH8M, /* ICH8M */
+ LPC_ICH8ME, /* ICH8M-E */
+ LPC_ICH9, /* ICH9 */
+ LPC_ICH9R, /* ICH9R */
+ LPC_ICH9DH, /* ICH9DH */
+ LPC_ICH9DO, /* ICH9DO */
+ LPC_ICH9M, /* ICH9M */
+ LPC_ICH9ME, /* ICH9M-E */
+ LPC_ICH10, /* ICH10 */
+ LPC_ICH10R, /* ICH10R */
+ LPC_ICH10D, /* ICH10D */
+ LPC_ICH10DO, /* ICH10DO */
+ LPC_PCH, /* PCH Desktop Full Featured */
+ LPC_PCHM, /* PCH Mobile Full Featured */
+ LPC_P55, /* P55 */
+ LPC_PM55, /* PM55 */
+ LPC_H55, /* H55 */
+ LPC_QM57, /* QM57 */
+ LPC_H57, /* H57 */
+ LPC_HM55, /* HM55 */
+ LPC_Q57, /* Q57 */
+ LPC_HM57, /* HM57 */
+ LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */
+ LPC_QS57, /* QS57 */
+ LPC_3400, /* 3400 */
+ LPC_3420, /* 3420 */
+ LPC_3450, /* 3450 */
+ LPC_EP80579, /* EP80579 */
+ LPC_CPT1, /* Cougar Point */
+ LPC_CPT2, /* Cougar Point Desktop */
+ LPC_CPT3, /* Cougar Point Mobile */
+ LPC_CPT4, /* Cougar Point */
+ LPC_CPT5, /* Cougar Point */
+ LPC_CPT6, /* Cougar Point */
+ LPC_CPT7, /* Cougar Point */
+ LPC_CPT8, /* Cougar Point */
+ LPC_CPT9, /* Cougar Point */
+ LPC_CPT10, /* Cougar Point */
+ LPC_CPT11, /* Cougar Point */
+ LPC_CPT12, /* Cougar Point */
+ LPC_CPT13, /* Cougar Point */
+ LPC_CPT14, /* Cougar Point */
+ LPC_CPT15, /* Cougar Point */
+ LPC_CPT16, /* Cougar Point */
+ LPC_CPT17, /* Cougar Point */
+ LPC_CPT18, /* Cougar Point */
+ LPC_CPT19, /* Cougar Point */
+ LPC_CPT20, /* Cougar Point */
+ LPC_CPT21, /* Cougar Point */
+ LPC_CPT22, /* Cougar Point */
+ LPC_CPT23, /* Cougar Point */
+ LPC_CPT24, /* Cougar Point */
+ LPC_CPT25, /* Cougar Point */
+ LPC_CPT26, /* Cougar Point */
+ LPC_CPT27, /* Cougar Point */
+ LPC_CPT28, /* Cougar Point */
+ LPC_CPT29, /* Cougar Point */
+ LPC_CPT30, /* Cougar Point */
+ LPC_CPT31, /* Cougar Point */
+ LPC_PBG1, /* Patsburg */
+ LPC_PBG2, /* Patsburg */
+ LPC_DH89XXCC, /* DH89xxCC */
+ LPC_PPT0, /* Panther Point */
+ LPC_PPT1, /* Panther Point */
+ LPC_PPT2, /* Panther Point */
+ LPC_PPT3, /* Panther Point */
+ LPC_PPT4, /* Panther Point */
+ LPC_PPT5, /* Panther Point */
+ LPC_PPT6, /* Panther Point */
+ LPC_PPT7, /* Panther Point */
+ LPC_PPT8, /* Panther Point */
+ LPC_PPT9, /* Panther Point */
+ LPC_PPT10, /* Panther Point */
+ LPC_PPT11, /* Panther Point */
+ LPC_PPT12, /* Panther Point */
+ LPC_PPT13, /* Panther Point */
+ LPC_PPT14, /* Panther Point */
+ LPC_PPT15, /* Panther Point */
+ LPC_PPT16, /* Panther Point */
+ LPC_PPT17, /* Panther Point */
+ LPC_PPT18, /* Panther Point */
+ LPC_PPT19, /* Panther Point */
+ LPC_PPT20, /* Panther Point */
+ LPC_PPT21, /* Panther Point */
+ LPC_PPT22, /* Panther Point */
+ LPC_PPT23, /* Panther Point */
+ LPC_PPT24, /* Panther Point */
+ LPC_PPT25, /* Panther Point */
+ LPC_PPT26, /* Panther Point */
+ LPC_PPT27, /* Panther Point */
+ LPC_PPT28, /* Panther Point */
+ LPC_PPT29, /* Panther Point */
+ LPC_PPT30, /* Panther Point */
+ LPC_PPT31, /* Panther Point */
+};
+
+struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
+ {"ICH", 0},
+ {"ICH0", 0},
+ {"ICH2", 0},
+ {"ICH2-M", 0},
+ {"ICH3-S", 0},
+ {"ICH3-M", 0},
+ {"ICH4", 0},
+ {"ICH4-M", 0},
+ {"C-ICH", 0},
+ {"ICH5 or ICH5R", 0},
+ {"6300ESB", 0},
+ {"ICH6 or ICH6R", 0x0601},
+ {"ICH6-M", 0x0601},
+ {"ICH6W or ICH6RW", 0x0601},
+ {"631xESB/632xESB", 0x0601},
+ {"ICH7 or ICH7R", 0x0701},
+ {"ICH7DH", 0x0701},
+ {"ICH7-M or ICH7-U", 0x0701},
+ {"ICH7-M DH", 0x0701},
+ {"NM10", 0},
+ {"ICH8 or ICH8R", 0x0701},
+ {"ICH8DH", 0x0701},
+ {"ICH8DO", 0x0701},
+ {"ICH8M", 0x0701},
+ {"ICH8M-E", 0x0701},
+ {"ICH9", 0x0801},
+ {"ICH9R", 0x0801},
+ {"ICH9DH", 0x0801},
+ {"ICH9DO", 0x0801},
+ {"ICH9M", 0x0801},
+ {"ICH9M-E", 0x0801},
+ {"ICH10", 0x0a11},
+ {"ICH10R", 0x0a11},
+ {"ICH10D", 0x0a01},
+ {"ICH10DO", 0x0a01},
+ {"PCH Desktop Full Featured", 0x0501},
+ {"PCH Mobile Full Featured", 0x0501},
+ {"P55", 0x0501},
+ {"PM55", 0x0501},
+ {"H55", 0x0501},
+ {"QM57", 0x0501},
+ {"H57", 0x0501},
+ {"HM55", 0x0501},
+ {"Q57", 0x0501},
+ {"HM57", 0x0501},
+ {"PCH Mobile SFF Full Featured", 0x0501},
+ {"QS57", 0x0501},
+ {"3400", 0x0501},
+ {"3420", 0x0501},
+ {"3450", 0x0501},
+ {"EP80579", 0},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Cougar Point", 0x0501},
+ {"Patsburg", 0},
+ {"Patsburg", 0},
+ {"DH89xxCC", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {"Panther Point", 0},
+ {NULL, 0}
+};
+EXPORT_SYMBOL(lpc_chipset_info);
+
+static DEFINE_PCI_DEVICE_TABLE(lpc_ich_ids) = {
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801AA_0, LPC_ICH)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801AB_0, LPC_ICH0)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801BA_0, LPC_ICH2)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801BA_10, LPC_ICH2M)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801CA_0, LPC_ICH3)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801CA_12, LPC_ICH3M)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801DB_0, LPC_ICH4)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801DB_12, LPC_ICH4M)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801E_0, LPC_CICH)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801EB_0, LPC_ICH5)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ESB_1, LPC_6300ESB)},
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_0, LPC_ICH6) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_1, LPC_ICH6M) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_2, LPC_ICH6W) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ESB2_0, LPC_631XESB) },
+ { ICHX_PDEV(0x2671, LPC_631XESB) },
+ { ICHX_PDEV(0x2672, LPC_631XESB) },
+ { ICHX_PDEV(0x2673, LPC_631XESB) },
+ { ICHX_PDEV(0x2674, LPC_631XESB) },
+ { ICHX_PDEV(0x2675, LPC_631XESB) },
+ { ICHX_PDEV(0x2676, LPC_631XESB) },
+ { ICHX_PDEV(0x2677, LPC_631XESB) },
+ { ICHX_PDEV(0x2678, LPC_631XESB) },
+ { ICHX_PDEV(0x2679, LPC_631XESB) },
+ { ICHX_PDEV(0x267a, LPC_631XESB) },
+ { ICHX_PDEV(0x267b, LPC_631XESB) },
+ { ICHX_PDEV(0x267c, LPC_631XESB) },
+ { ICHX_PDEV(0x267d, LPC_631XESB) },
+ { ICHX_PDEV(0x267e, LPC_631XESB) },
+ { ICHX_PDEV(0x267f, LPC_631XESB) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_0, LPC_ICH7) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_30, LPC_ICH7DH) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_1, LPC_ICH7M) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_31, LPC_ICH7MDH) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_TGP_LPC, LPC_NM10) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_0, LPC_ICH8) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_2, LPC_ICH8DH) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_3, LPC_ICH8DO) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_4, LPC_ICH8M) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_1, LPC_ICH8ME) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_8, LPC_ICH9) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_7, LPC_ICH9R) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_2, LPC_ICH9DH) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_4, LPC_ICH9DO) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_5, LPC_ICH9M) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_1, LPC_ICH9ME) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_2, LPC_ICH10) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_1, LPC_ICH10R) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_3, LPC_ICH10D) },
+ { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_0, LPC_ICH10DO) },
+ { ICHX_PDEV(0x3b00, LPC_PCH) },
+ { ICHX_PDEV(0x3b01, LPC_PCHM) },
+ { ICHX_PDEV(0x3b02, LPC_P55) },
+ { ICHX_PDEV(0x3b03, LPC_PM55) },
+ { ICHX_PDEV(0x3b06, LPC_H55) },
+ { ICHX_PDEV(0x3b07, LPC_QM57) },
+ { ICHX_PDEV(0x3b08, LPC_H57) },
+ { ICHX_PDEV(0x3b09, LPC_HM55) },
+ { ICHX_PDEV(0x3b0a, LPC_Q57) },
+ { ICHX_PDEV(0x3b0b, LPC_HM57) },
+ { ICHX_PDEV(0x3b0d, LPC_PCHMSFF) },
+ { ICHX_PDEV(0x3b0f, LPC_QS57) },
+ { ICHX_PDEV(0x3b12, LPC_3400) },
+ { ICHX_PDEV(0x3b14, LPC_3420) },
+ { ICHX_PDEV(0x3b16, LPC_3450) },
+ { ICHX_PDEV(0x5031, LPC_EP80579) },
+ { ICHX_PDEV(0x1c41, LPC_CPT1) },
+ { ICHX_PDEV(0x1c42, LPC_CPT2) },
+ { ICHX_PDEV(0x1c43, LPC_CPT3) },
+ { ICHX_PDEV(0x1c44, LPC_CPT4) },
+ { ICHX_PDEV(0x1c45, LPC_CPT5) },
+ { ICHX_PDEV(0x1c46, LPC_CPT6) },
+ { ICHX_PDEV(0x1c47, LPC_CPT7) },
+ { ICHX_PDEV(0x1c48, LPC_CPT8) },
+ { ICHX_PDEV(0x1c49, LPC_CPT9) },
+ { ICHX_PDEV(0x1c4a, LPC_CPT10) },
+ { ICHX_PDEV(0x1c4b, LPC_CPT11) },
+ { ICHX_PDEV(0x1c4c, LPC_CPT12) },
+ { ICHX_PDEV(0x1c4d, LPC_CPT13) },
+ { ICHX_PDEV(0x1c4e, LPC_CPT14) },
+ { ICHX_PDEV(0x1c4f, LPC_CPT15) },
+ { ICHX_PDEV(0x1c50, LPC_CPT16) },
+ { ICHX_PDEV(0x1c51, LPC_CPT17) },
+ { ICHX_PDEV(0x1c52, LPC_CPT18) },
+ { ICHX_PDEV(0x1c53, LPC_CPT19) },
+ { ICHX_PDEV(0x1c54, LPC_CPT20) },
+ { ICHX_PDEV(0x1c55, LPC_CPT21) },
+ { ICHX_PDEV(0x1c56, LPC_CPT22) },
+ { ICHX_PDEV(0x1c57, LPC_CPT23) },
+ { ICHX_PDEV(0x1c58, LPC_CPT24) },
+ { ICHX_PDEV(0x1c59, LPC_CPT25) },
+ { ICHX_PDEV(0x1c5a, LPC_CPT26) },
+ { ICHX_PDEV(0x1c5b, LPC_CPT27) },
+ { ICHX_PDEV(0x1c5c, LPC_CPT28) },
+ { ICHX_PDEV(0x1c5d, LPC_CPT29) },
+ { ICHX_PDEV(0x1c5e, LPC_CPT30) },
+ { ICHX_PDEV(0x1c5f, LPC_CPT31) },
+ { ICHX_PDEV(0x1d40, LPC_PBG1) },
+ { ICHX_PDEV(0x1d41, LPC_PBG2) },
+ { ICHX_PDEV(0x2310, LPC_DH89XXCC) },
+ { ICHX_PDEV(0x1e40, LPC_PPT0) },
+ { ICHX_PDEV(0x1e41, LPC_PPT1) },
+ { ICHX_PDEV(0x1e42, LPC_PPT2) },
+ { ICHX_PDEV(0x1e43, LPC_PPT3) },
+ { ICHX_PDEV(0x1e44, LPC_PPT4) },
+ { ICHX_PDEV(0x1e45, LPC_PPT5) },
+ { ICHX_PDEV(0x1e46, LPC_PPT6) },
+ { ICHX_PDEV(0x1e47, LPC_PPT7) },
+ { ICHX_PDEV(0x1e48, LPC_PPT8) },
+ { ICHX_PDEV(0x1e49, LPC_PPT9) },
+ { ICHX_PDEV(0x1e4a, LPC_PPT10) },
+ { ICHX_PDEV(0x1e4b, LPC_PPT11) },
+ { ICHX_PDEV(0x1e4c, LPC_PPT12) },
+ { ICHX_PDEV(0x1e4d, LPC_PPT13) },
+ { ICHX_PDEV(0x1e4e, LPC_PPT14) },
+ { ICHX_PDEV(0x1e4f, LPC_PPT15) },
+ { ICHX_PDEV(0x1e50, LPC_PPT16) },
+ { ICHX_PDEV(0x1e51, LPC_PPT17) },
+ { ICHX_PDEV(0x1e52, LPC_PPT18) },
+ { ICHX_PDEV(0x1e53, LPC_PPT19) },
+ { ICHX_PDEV(0x1e54, LPC_PPT20) },
+ { ICHX_PDEV(0x1e55, LPC_PPT21) },
+ { ICHX_PDEV(0x1e56, LPC_PPT22) },
+ { ICHX_PDEV(0x1e57, LPC_PPT23) },
+ { ICHX_PDEV(0x1e58, LPC_PPT24) },
+ { ICHX_PDEV(0x1e59, LPC_PPT25) },
+ { ICHX_PDEV(0x1e5a, LPC_PPT26) },
+ { ICHX_PDEV(0x1e5b, LPC_PPT27) },
+ { ICHX_PDEV(0x1e5c, LPC_PPT28) },
+ { ICHX_PDEV(0x1e5d, LPC_PPT29) },
+ { ICHX_PDEV(0x1e5e, LPC_PPT30) },
+ { ICHX_PDEV(0x1e5f, LPC_PPT31) },
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, lpc_ich_ids);
+
+static int __devinit lpc_ich_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ u32 base_addr_cfg;
+ u32 base_addr;
+ int i;
+
+ /* Setup power management base register */
+ pci_read_config_dword(dev, ACPIBASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0x0000ff80;
+ if (!base_addr) {
+ dev_err(&dev->dev, "I/O space for ACPI uninitialized\n");
+ return -ENODEV;
+ }
+
+ gpio_ich_res[ICH_RES_GPE0].start = base_addr + ACPIBASE_GPE_OFF;
+ gpio_ich_res[ICH_RES_GPE0].end = base_addr + ACPIBASE_GPE_END;
+
+ /* Enable LPC ACPI space */
+ pci_read_config_byte(dev, ACPICTRL, &lpc_ich_acpi_save);
+ pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save | 0x10);
+
+ /* Setup GPIO base register */
+ pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0x0000ff80;
+ if (!base_addr) {
+ dev_err(&dev->dev, "I/O space for GPIO uninitialized\n");
+ return -ENODEV;
+ }
+
+ gpio_ich_res[ICH_RES_GPIO].start = base_addr;
+ gpio_ich_res[ICH_RES_GPIO].end = base_addr + GPIOBASE_IO_SIZE - 1;
+
+ /* Enable LPC GPIO space */
+ pci_read_config_byte(dev, GPIOCTRL, &lpc_ich_gpio_save);
+ pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save | 0x10);
+
+ for (i=0; i < ARRAY_SIZE(lpc_ich_cells); i++)
+ lpc_ich_cells[i].id = id->driver_data;
+
+ return mfd_add_devices(&dev->dev, 0,
+ lpc_ich_cells, ARRAY_SIZE(lpc_ich_cells), NULL, 0);
+}
+
+static void __devexit lpc_ich_remove(struct pci_dev *dev)
+{
+ mfd_remove_devices(&dev->dev);
+
+ pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save);
+ pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save);
+}
+
+static struct pci_driver lpc_ich_driver = {
+ .name = "lpc_ich",
+ .id_table = lpc_ich_ids,
+ .probe = lpc_ich_probe,
+ .remove = __devexit_p(lpc_ich_remove),
+};
+
+static int __init lpc_ich_init(void)
+{
+ return pci_register_driver(&lpc_ich_driver);
+}
+
+static void __exit lpc_ich_exit(void)
+{
+ pci_unregister_driver(&lpc_ich_driver);
+}
+
+module_init(lpc_ich_init);
+module_exit(lpc_ich_exit);
+
+MODULE_AUTHOR("Aaron Sierra <[email protected]>");
+MODULE_DESCRIPTION("LPC interface for Intel ICH");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
new file mode 100644
index 0000000..27a1c32
--- /dev/null
+++ b/include/linux/mfd/lpc_ich.h
@@ -0,0 +1,33 @@
+/*
+ * linux/drivers/mfd/lpc_ich.h
+ *
+ * Copyright (c) 2012 Extreme Engineering Solution, Inc.
+ * Author: Aaron Sierra <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef LPC_ICH_H
+#define LPC_ICH_H
+
+/* GPIO resources */
+#define ICH_RES_GPIO 0
+#define ICH_RES_GPE0 1
+
+struct lpc_ich_info {
+ char *name;
+ unsigned int gpio_version;
+};
+
+extern struct lpc_ich_info lpc_chipset_info[];
+#endif
--
1.7.0.4
On Thu, Feb 02, 2012 at 06:25:14PM -0500, Aaron Sierra wrote:
> This driver currently creates resources for use by a forthcoming ICH
> chipset GPIO driver. It could be expanded to created the resources for
> converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
> drivers to use the mfd model.
>
> Signed-off-by: Aaron Sierra <[email protected]>
Hi Aaron,
I am having trouble applying your patch series. What is your baseline ?
Thanks,
Guenter
> I am having trouble applying your patch series. What is your baseline
> ?
>
> Thanks,
> Guenter
Guenter,
I should have mentioned that these three patches are based on 3.0.4.
--
--
--- Extreme Engineering Solutions, Inc (X-ES) ---
Aaron Sierra
X-ES http://www.xes-inc.com
3225 Deming Way, Ste 120
Middleton, WI 53562
Phone: (608) 833-1155 x115
Email: [email protected]
On Thu, 2012-02-02 at 18:25 -0500, Aaron Sierra wrote:
> This driver currently creates resources for use by a forthcoming ICH
> chipset GPIO driver. It could be expanded to created the resources for
> converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
> drivers to use the mfd model.
>
> Signed-off-by: Aaron Sierra <[email protected]>
Hi Aaron,
looks pretty good. Couple of comments below.
> ---
> drivers/mfd/Kconfig | 9 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/lpc_ich.c | 531 +++++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/lpc_ich.h | 33 +++
> 4 files changed, 574 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mfd/lpc_ich.c
> create mode 100644 include/linux/mfd/lpc_ich.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 6ca938a..18eca82 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -636,6 +636,15 @@ config LPC_SCH
> LPC bridge function of the Intel SCH provides support for
> System Management Bus and General Purpose I/O.
>
> +config LPC_ICH
> + tristate "Intel ICH LPC"
> + depends on PCI
> + select MFD_CORE
> + help
> + The LPC bridge function of the Intel ICH provides support for
> + many functional units. This driver provides needed support for
> + other drivers to control these functions, currently GPIO.
> +
> config MFD_RDC321X
> tristate "Support for RDC-R321x southbridge"
> select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index d7d47d2..d479882 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -83,6 +83,7 @@ obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o
> obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
> obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
> obj-$(CONFIG_LPC_SCH) += lpc_sch.o
> +obj-$(CONFIG_LPC_ICH) += lpc_ich.o
> obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
> obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
> obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
> diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
> new file mode 100644
> index 0000000..a288c74
> --- /dev/null
> +++ b/drivers/mfd/lpc_ich.c
> @@ -0,0 +1,531 @@
> +/*
> + * lpc_ich.c - LPC interface for Intel ICH
> + *
> + * LPC bridge function of the Intel ICH contains many other
> + * functional units, such as Interrupt controllers, Timers,
> + * Power Management, System Management, GPIO, RTC, and LPC
> + * Configuration Registers.
> + *
> + * This driver is derived from lpc_sch.
> +
> + * Copyright (c) 2011 Extreme Engineering Solution, Inc.
> + * Author: Aaron Sierra <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License 2 as published
> + * by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; see the file COPYING. If not, write to
> + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
It might make sense to list the Intel document numbers, like iTCO_wdt.c.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/errno.h>
> +#include <linux/acpi.h>
> +#include <linux/pci.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/lpc_ich.h>
> +
> +/* Convenient wrapper to make our PCI ID table */
> +#define ICHX_PDEV(dev, idx) \
> + .vendor = PCI_VENDOR_ID_INTEL, \
> + .device = dev, \
> + .subvendor = PCI_ANY_ID, \
> + .subdevice = PCI_ANY_ID, \
> + .class = 0, \
> + .class_mask = 0, \
> + .driver_data = idx
> +
Later kernel versions have a macro PCI_VDEVICE which can be used instead
(and is now used by iTCO_wdt.c).
> +#define ACPIBASE 0x40
> +#define ACPIBASE_GPE_OFF 0x20
> +#define ACPIBASE_GPE_END 0x2f
> +#define ACPICTRL 0x44
> +
> +#define GPIOBASE 0x48
> +#define GPIOCTRL 0x4C
> +#define GPIOBASE_IO_SIZE 0x80
> +
> +static u8 lpc_ich_acpi_save, lpc_ich_gpio_save;
> +
> +static struct resource gpio_ich_res[] = {
> + /* BASE */
> + {
> + .flags = IORESOURCE_IO,
> + },
> + /* ACPI */
> + {
> + .flags = IORESOURCE_IO,
> + },
> +};
> +
> +static struct mfd_cell lpc_ich_cells[] = {
> + {
> + .name = "ich_gpio",
> + .num_resources = ARRAY_SIZE(gpio_ich_res),
> + .resources = gpio_ich_res,
> + },
> +};
> +
> +/* TCO related info */
s/TCO/chipset/
> +enum lpc_chipsets {
> + LPC_ICH = 0, /* ICH */
> + LPC_ICH0, /* ICH0 */
> + LPC_ICH2, /* ICH2 */
> + LPC_ICH2M, /* ICH2-M */
> + LPC_ICH3, /* ICH3-S */
> + LPC_ICH3M, /* ICH3-M */
> + LPC_ICH4, /* ICH4 */
> + LPC_ICH4M, /* ICH4-M */
> + LPC_CICH, /* C-ICH */
> + LPC_ICH5, /* ICH5 & ICH5R */
> + LPC_6300ESB, /* 6300ESB */
> + LPC_ICH6, /* ICH6 & ICH6R */
> + LPC_ICH6M, /* ICH6-M */
> + LPC_ICH6W, /* ICH6W & ICH6RW */
> + LPC_631XESB, /* 631xESB/632xESB */
> + LPC_ICH7, /* ICH7 & ICH7R */
> + LPC_ICH7DH, /* ICH7DH */
> + LPC_ICH7M, /* ICH7-M & ICH7-U */
> + LPC_ICH7MDH, /* ICH7-M DH */
> + LPC_NM10, /* NM10 */
> + LPC_ICH8, /* ICH8 & ICH8R */
> + LPC_ICH8DH, /* ICH8DH */
> + LPC_ICH8DO, /* ICH8DO */
> + LPC_ICH8M, /* ICH8M */
> + LPC_ICH8ME, /* ICH8M-E */
> + LPC_ICH9, /* ICH9 */
> + LPC_ICH9R, /* ICH9R */
> + LPC_ICH9DH, /* ICH9DH */
> + LPC_ICH9DO, /* ICH9DO */
> + LPC_ICH9M, /* ICH9M */
> + LPC_ICH9ME, /* ICH9M-E */
> + LPC_ICH10, /* ICH10 */
> + LPC_ICH10R, /* ICH10R */
> + LPC_ICH10D, /* ICH10D */
> + LPC_ICH10DO, /* ICH10DO */
> + LPC_PCH, /* PCH Desktop Full Featured */
> + LPC_PCHM, /* PCH Mobile Full Featured */
> + LPC_P55, /* P55 */
> + LPC_PM55, /* PM55 */
> + LPC_H55, /* H55 */
> + LPC_QM57, /* QM57 */
> + LPC_H57, /* H57 */
> + LPC_HM55, /* HM55 */
> + LPC_Q57, /* Q57 */
> + LPC_HM57, /* HM57 */
> + LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */
> + LPC_QS57, /* QS57 */
> + LPC_3400, /* 3400 */
> + LPC_3420, /* 3420 */
> + LPC_3450, /* 3450 */
> + LPC_EP80579, /* EP80579 */
> + LPC_CPT1, /* Cougar Point */
> + LPC_CPT2, /* Cougar Point Desktop */
> + LPC_CPT3, /* Cougar Point Mobile */
> + LPC_CPT4, /* Cougar Point */
> + LPC_CPT5, /* Cougar Point */
> + LPC_CPT6, /* Cougar Point */
> + LPC_CPT7, /* Cougar Point */
> + LPC_CPT8, /* Cougar Point */
> + LPC_CPT9, /* Cougar Point */
> + LPC_CPT10, /* Cougar Point */
> + LPC_CPT11, /* Cougar Point */
> + LPC_CPT12, /* Cougar Point */
> + LPC_CPT13, /* Cougar Point */
> + LPC_CPT14, /* Cougar Point */
> + LPC_CPT15, /* Cougar Point */
> + LPC_CPT16, /* Cougar Point */
> + LPC_CPT17, /* Cougar Point */
> + LPC_CPT18, /* Cougar Point */
> + LPC_CPT19, /* Cougar Point */
> + LPC_CPT20, /* Cougar Point */
> + LPC_CPT21, /* Cougar Point */
> + LPC_CPT22, /* Cougar Point */
> + LPC_CPT23, /* Cougar Point */
> + LPC_CPT24, /* Cougar Point */
> + LPC_CPT25, /* Cougar Point */
> + LPC_CPT26, /* Cougar Point */
> + LPC_CPT27, /* Cougar Point */
> + LPC_CPT28, /* Cougar Point */
> + LPC_CPT29, /* Cougar Point */
> + LPC_CPT30, /* Cougar Point */
> + LPC_CPT31, /* Cougar Point */
> + LPC_PBG1, /* Patsburg */
> + LPC_PBG2, /* Patsburg */
> + LPC_DH89XXCC, /* DH89xxCC */
> + LPC_PPT0, /* Panther Point */
> + LPC_PPT1, /* Panther Point */
> + LPC_PPT2, /* Panther Point */
> + LPC_PPT3, /* Panther Point */
> + LPC_PPT4, /* Panther Point */
> + LPC_PPT5, /* Panther Point */
> + LPC_PPT6, /* Panther Point */
> + LPC_PPT7, /* Panther Point */
> + LPC_PPT8, /* Panther Point */
> + LPC_PPT9, /* Panther Point */
> + LPC_PPT10, /* Panther Point */
> + LPC_PPT11, /* Panther Point */
> + LPC_PPT12, /* Panther Point */
> + LPC_PPT13, /* Panther Point */
> + LPC_PPT14, /* Panther Point */
> + LPC_PPT15, /* Panther Point */
> + LPC_PPT16, /* Panther Point */
> + LPC_PPT17, /* Panther Point */
> + LPC_PPT18, /* Panther Point */
> + LPC_PPT19, /* Panther Point */
> + LPC_PPT20, /* Panther Point */
> + LPC_PPT21, /* Panther Point */
> + LPC_PPT22, /* Panther Point */
> + LPC_PPT23, /* Panther Point */
> + LPC_PPT24, /* Panther Point */
> + LPC_PPT25, /* Panther Point */
> + LPC_PPT26, /* Panther Point */
> + LPC_PPT27, /* Panther Point */
> + LPC_PPT28, /* Panther Point */
> + LPC_PPT29, /* Panther Point */
> + LPC_PPT30, /* Panther Point */
> + LPC_PPT31, /* Panther Point */
> +};
> +
I was always irritated by those duplicate defines and array entries in
iTCO_wdt.c. Apparently someone else too, because it is gone in the
latest version of iTCO_wdt.c. Should do the same here.
> +struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
__devinitdata is not a good idea for a to-be-exported data structure.
Though it does not need to be exported (see below), so that is really a
moot point.
> + {"ICH", 0},
> + {"ICH0", 0},
> + {"ICH2", 0},
> + {"ICH2-M", 0},
> + {"ICH3-S", 0},
> + {"ICH3-M", 0},
> + {"ICH4", 0},
> + {"ICH4-M", 0},
> + {"C-ICH", 0},
> + {"ICH5 or ICH5R", 0},
> + {"6300ESB", 0},
> + {"ICH6 or ICH6R", 0x0601},
> + {"ICH6-M", 0x0601},
> + {"ICH6W or ICH6RW", 0x0601},
> + {"631xESB/632xESB", 0x0601},
> + {"ICH7 or ICH7R", 0x0701},
> + {"ICH7DH", 0x0701},
> + {"ICH7-M or ICH7-U", 0x0701},
> + {"ICH7-M DH", 0x0701},
> + {"NM10", 0},
> + {"ICH8 or ICH8R", 0x0701},
> + {"ICH8DH", 0x0701},
> + {"ICH8DO", 0x0701},
> + {"ICH8M", 0x0701},
> + {"ICH8M-E", 0x0701},
> + {"ICH9", 0x0801},
> + {"ICH9R", 0x0801},
> + {"ICH9DH", 0x0801},
> + {"ICH9DO", 0x0801},
> + {"ICH9M", 0x0801},
> + {"ICH9M-E", 0x0801},
> + {"ICH10", 0x0a11},
> + {"ICH10R", 0x0a11},
> + {"ICH10D", 0x0a01},
> + {"ICH10DO", 0x0a01},
> + {"PCH Desktop Full Featured", 0x0501},
> + {"PCH Mobile Full Featured", 0x0501},
> + {"P55", 0x0501},
> + {"PM55", 0x0501},
> + {"H55", 0x0501},
> + {"QM57", 0x0501},
> + {"H57", 0x0501},
> + {"HM55", 0x0501},
> + {"Q57", 0x0501},
> + {"HM57", 0x0501},
> + {"PCH Mobile SFF Full Featured", 0x0501},
> + {"QS57", 0x0501},
> + {"3400", 0x0501},
> + {"3420", 0x0501},
> + {"3450", 0x0501},
> + {"EP80579", 0},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Cougar Point", 0x0501},
> + {"Patsburg", 0},
> + {"Patsburg", 0},
> + {"DH89xxCC", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {"Panther Point", 0},
> + {NULL, 0}
The last (empty) entry is not needed here, since the table is indexed by
enum lpc_chipsets. Also, it might be better to use explicit
initialization such as
[LPC_PPT] = {"Panther Point", 0},
since that would ensure that entries can not get out of sync.
> +};
> +EXPORT_SYMBOL(lpc_chipset_info);
> +
No need to export the table. See below.
> +static DEFINE_PCI_DEVICE_TABLE(lpc_ich_ids) = {
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801AA_0, LPC_ICH)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801AB_0, LPC_ICH0)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801BA_0, LPC_ICH2)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801BA_10, LPC_ICH2M)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801CA_0, LPC_ICH3)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801CA_12, LPC_ICH3M)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801DB_0, LPC_ICH4)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801DB_12, LPC_ICH4M)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801E_0, LPC_CICH)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_82801EB_0, LPC_ICH5)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ESB_1, LPC_6300ESB)},
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_0, LPC_ICH6) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_1, LPC_ICH6M) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH6_2, LPC_ICH6W) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ESB2_0, LPC_631XESB) },
The latest version if iTCO_wdt.c got rid of the symbolic defines. Maybe
follow its lead.
> + { ICHX_PDEV(0x2671, LPC_631XESB) },
> + { ICHX_PDEV(0x2672, LPC_631XESB) },
> + { ICHX_PDEV(0x2673, LPC_631XESB) },
> + { ICHX_PDEV(0x2674, LPC_631XESB) },
> + { ICHX_PDEV(0x2675, LPC_631XESB) },
> + { ICHX_PDEV(0x2676, LPC_631XESB) },
> + { ICHX_PDEV(0x2677, LPC_631XESB) },
> + { ICHX_PDEV(0x2678, LPC_631XESB) },
> + { ICHX_PDEV(0x2679, LPC_631XESB) },
> + { ICHX_PDEV(0x267a, LPC_631XESB) },
> + { ICHX_PDEV(0x267b, LPC_631XESB) },
> + { ICHX_PDEV(0x267c, LPC_631XESB) },
> + { ICHX_PDEV(0x267d, LPC_631XESB) },
> + { ICHX_PDEV(0x267e, LPC_631XESB) },
> + { ICHX_PDEV(0x267f, LPC_631XESB) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_0, LPC_ICH7) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_30, LPC_ICH7DH) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_1, LPC_ICH7M) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH7_31, LPC_ICH7MDH) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_TGP_LPC, LPC_NM10) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_0, LPC_ICH8) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_2, LPC_ICH8DH) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_3, LPC_ICH8DO) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_4, LPC_ICH8M) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH8_1, LPC_ICH8ME) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_8, LPC_ICH9) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_7, LPC_ICH9R) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_2, LPC_ICH9DH) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_4, LPC_ICH9DO) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_5, LPC_ICH9M) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH9_1, LPC_ICH9ME) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_2, LPC_ICH10) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_1, LPC_ICH10R) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_3, LPC_ICH10D) },
> + { ICHX_PDEV(PCI_DEVICE_ID_INTEL_ICH10_0, LPC_ICH10DO) },
> + { ICHX_PDEV(0x3b00, LPC_PCH) },
> + { ICHX_PDEV(0x3b01, LPC_PCHM) },
> + { ICHX_PDEV(0x3b02, LPC_P55) },
> + { ICHX_PDEV(0x3b03, LPC_PM55) },
> + { ICHX_PDEV(0x3b06, LPC_H55) },
> + { ICHX_PDEV(0x3b07, LPC_QM57) },
> + { ICHX_PDEV(0x3b08, LPC_H57) },
> + { ICHX_PDEV(0x3b09, LPC_HM55) },
> + { ICHX_PDEV(0x3b0a, LPC_Q57) },
> + { ICHX_PDEV(0x3b0b, LPC_HM57) },
> + { ICHX_PDEV(0x3b0d, LPC_PCHMSFF) },
> + { ICHX_PDEV(0x3b0f, LPC_QS57) },
> + { ICHX_PDEV(0x3b12, LPC_3400) },
> + { ICHX_PDEV(0x3b14, LPC_3420) },
> + { ICHX_PDEV(0x3b16, LPC_3450) },
> + { ICHX_PDEV(0x5031, LPC_EP80579) },
> + { ICHX_PDEV(0x1c41, LPC_CPT1) },
> + { ICHX_PDEV(0x1c42, LPC_CPT2) },
> + { ICHX_PDEV(0x1c43, LPC_CPT3) },
> + { ICHX_PDEV(0x1c44, LPC_CPT4) },
> + { ICHX_PDEV(0x1c45, LPC_CPT5) },
> + { ICHX_PDEV(0x1c46, LPC_CPT6) },
> + { ICHX_PDEV(0x1c47, LPC_CPT7) },
> + { ICHX_PDEV(0x1c48, LPC_CPT8) },
> + { ICHX_PDEV(0x1c49, LPC_CPT9) },
> + { ICHX_PDEV(0x1c4a, LPC_CPT10) },
> + { ICHX_PDEV(0x1c4b, LPC_CPT11) },
> + { ICHX_PDEV(0x1c4c, LPC_CPT12) },
> + { ICHX_PDEV(0x1c4d, LPC_CPT13) },
> + { ICHX_PDEV(0x1c4e, LPC_CPT14) },
> + { ICHX_PDEV(0x1c4f, LPC_CPT15) },
> + { ICHX_PDEV(0x1c50, LPC_CPT16) },
> + { ICHX_PDEV(0x1c51, LPC_CPT17) },
> + { ICHX_PDEV(0x1c52, LPC_CPT18) },
> + { ICHX_PDEV(0x1c53, LPC_CPT19) },
> + { ICHX_PDEV(0x1c54, LPC_CPT20) },
> + { ICHX_PDEV(0x1c55, LPC_CPT21) },
> + { ICHX_PDEV(0x1c56, LPC_CPT22) },
> + { ICHX_PDEV(0x1c57, LPC_CPT23) },
> + { ICHX_PDEV(0x1c58, LPC_CPT24) },
> + { ICHX_PDEV(0x1c59, LPC_CPT25) },
> + { ICHX_PDEV(0x1c5a, LPC_CPT26) },
> + { ICHX_PDEV(0x1c5b, LPC_CPT27) },
> + { ICHX_PDEV(0x1c5c, LPC_CPT28) },
> + { ICHX_PDEV(0x1c5d, LPC_CPT29) },
> + { ICHX_PDEV(0x1c5e, LPC_CPT30) },
> + { ICHX_PDEV(0x1c5f, LPC_CPT31) },
> + { ICHX_PDEV(0x1d40, LPC_PBG1) },
> + { ICHX_PDEV(0x1d41, LPC_PBG2) },
> + { ICHX_PDEV(0x2310, LPC_DH89XXCC) },
> + { ICHX_PDEV(0x1e40, LPC_PPT0) },
> + { ICHX_PDEV(0x1e41, LPC_PPT1) },
> + { ICHX_PDEV(0x1e42, LPC_PPT2) },
> + { ICHX_PDEV(0x1e43, LPC_PPT3) },
> + { ICHX_PDEV(0x1e44, LPC_PPT4) },
> + { ICHX_PDEV(0x1e45, LPC_PPT5) },
> + { ICHX_PDEV(0x1e46, LPC_PPT6) },
> + { ICHX_PDEV(0x1e47, LPC_PPT7) },
> + { ICHX_PDEV(0x1e48, LPC_PPT8) },
> + { ICHX_PDEV(0x1e49, LPC_PPT9) },
> + { ICHX_PDEV(0x1e4a, LPC_PPT10) },
> + { ICHX_PDEV(0x1e4b, LPC_PPT11) },
> + { ICHX_PDEV(0x1e4c, LPC_PPT12) },
> + { ICHX_PDEV(0x1e4d, LPC_PPT13) },
> + { ICHX_PDEV(0x1e4e, LPC_PPT14) },
> + { ICHX_PDEV(0x1e4f, LPC_PPT15) },
> + { ICHX_PDEV(0x1e50, LPC_PPT16) },
> + { ICHX_PDEV(0x1e51, LPC_PPT17) },
> + { ICHX_PDEV(0x1e52, LPC_PPT18) },
> + { ICHX_PDEV(0x1e53, LPC_PPT19) },
> + { ICHX_PDEV(0x1e54, LPC_PPT20) },
> + { ICHX_PDEV(0x1e55, LPC_PPT21) },
> + { ICHX_PDEV(0x1e56, LPC_PPT22) },
> + { ICHX_PDEV(0x1e57, LPC_PPT23) },
> + { ICHX_PDEV(0x1e58, LPC_PPT24) },
> + { ICHX_PDEV(0x1e59, LPC_PPT25) },
> + { ICHX_PDEV(0x1e5a, LPC_PPT26) },
> + { ICHX_PDEV(0x1e5b, LPC_PPT27) },
> + { ICHX_PDEV(0x1e5c, LPC_PPT28) },
> + { ICHX_PDEV(0x1e5d, LPC_PPT29) },
> + { ICHX_PDEV(0x1e5e, LPC_PPT30) },
> + { ICHX_PDEV(0x1e5f, LPC_PPT31) },
ITCO_wdt.c now also supports Lynx Point.
> + { 0, },
> +};
> +MODULE_DEVICE_TABLE(pci, lpc_ich_ids);
> +
> +static int __devinit lpc_ich_probe(struct pci_dev *dev,
> + const struct pci_device_id *id)
> +{
> + u32 base_addr_cfg;
> + u32 base_addr;
> + int i;
> +
> + /* Setup power management base register */
> + pci_read_config_dword(dev, ACPIBASE, &base_addr_cfg);
> + base_addr = base_addr_cfg & 0x0000ff80;
> + if (!base_addr) {
> + dev_err(&dev->dev, "I/O space for ACPI uninitialized\n");
> + return -ENODEV;
> + }
> +
> + gpio_ich_res[ICH_RES_GPE0].start = base_addr + ACPIBASE_GPE_OFF;
> + gpio_ich_res[ICH_RES_GPE0].end = base_addr + ACPIBASE_GPE_END;
> +
> + /* Enable LPC ACPI space */
> + pci_read_config_byte(dev, ACPICTRL, &lpc_ich_acpi_save);
> + pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save | 0x10);
> +
> + /* Setup GPIO base register */
> + pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
> + base_addr = base_addr_cfg & 0x0000ff80;
> + if (!base_addr) {
> + dev_err(&dev->dev, "I/O space for GPIO uninitialized\n");
> + return -ENODEV;
> + }
> +
> + gpio_ich_res[ICH_RES_GPIO].start = base_addr;
> + gpio_ich_res[ICH_RES_GPIO].end = base_addr + GPIOBASE_IO_SIZE - 1;
> +
> + /* Enable LPC GPIO space */
> + pci_read_config_byte(dev, GPIOCTRL, &lpc_ich_gpio_save);
> + pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save | 0x10);
> +
> + for (i=0; i < ARRAY_SIZE(lpc_ich_cells); i++)
checkpatch error (should be 'i = 0').
> + lpc_ich_cells[i].id = id->driver_data;
> +
It would be better to use
lpc_ich_cells[i].platform_data = &lpc_chipset_info[id->driver_data];
lpc_ich_cells[i].pdata_size = sizeof(struct lpc_ich_info);
ie pass the structure in the cell's platform_data. This way you can
avoid the need to export lpc_chipset_info, and you don't have to keep it
around either.
You don't need mfd_cell.platform_data to pass the pci_device pointer,
since the child driver can get it using
"to_pci_dev(platform_device->dev.parent)".
I ended up making all the changes suggested above (plus the matching
changes needed in the subsequent patches) while I rebased the code to
3.3-rc2. Right now I have an (untested) patchset which applies to
3.3-rc2. Please let me know how you would like to proceed; I can send it
to you if you like, or to the list.
Thanks,
Guenter
> I ended up making all the changes suggested above (plus the matching
> changes needed in the subsequent patches) while I rebased the code to
> 3.3-rc2. Right now I have an (untested) patchset which applies to
> 3.3-rc2. Please let me know how you would like to proceed; I can send
> it to you if you like, or to the list.
Hey Guenter,
If you want to send me your patchset directly, I can test it out on
some of our hardware (Ibex Peak and Cougar Point). I can work on
implementing some of your other suggestions as well before
resubmitting to the list.
-Aaron
On Fri, 2012-02-03 at 14:35 -0500, Aaron Sierra wrote:
> > I ended up making all the changes suggested above (plus the matching
> > changes needed in the subsequent patches) while I rebased the code to
> > 3.3-rc2. Right now I have an (untested) patchset which applies to
> > 3.3-rc2. Please let me know how you would like to proceed; I can send
> > it to you if you like, or to the list.
>
> Hey Guenter,
> If you want to send me your patchset directly, I can test it out on
> some of our hardware (Ibex Peak and Cougar Point). I can work on
> implementing some of your other suggestions as well before
> resubmitting to the list.
>
Done. You should have received a series of four patches, your three plus
one with a number of changes I made on top of your first patch. The last
two patches are essentially yours modified to compile with 3.3-rc2 and
to work (hopefully ;) with my patch.
Thanks,
Guenter
Hi Aaron,
On Thu, 2012-02-02 at 18:27 -0500, Aaron Sierra wrote:
> This driver works on many Intel chipsets, including the ICH6, ICH7,
> ICH8, ICH9, ICH10, 3100, Series 5/3400 (Ibex Peak), Series 6/C200
> (Cougar Point), and NM10 (Tiger Point).
>
> Additional Intel chipsets should be easily supported if needed, eg the
> ICH1-5, EP80579, etc.
>
> Tested on QM67 (Cougar Point), QM57 (Ibex Peak), 3100 (Whitmore Lake),
> and NM10 (Tiger Point).
>
> Signed-off-by: Peter Tyser <[email protected]>
Needs rebase to 3.3.
s/ich_gpio/gpio-ich/ for file names.
I already commented on the way parameters are passed from the mfd
driver.
> ---
> MAINTAINERS | 6 +
> drivers/gpio/Kconfig | 16 ++
> drivers/gpio/Makefile | 1 +
> drivers/gpio/ich_gpio.c | 433 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 456 insertions(+), 0 deletions(-)
> create mode 100644 drivers/gpio/ich_gpio.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5738c8b..4905dd9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3160,6 +3160,12 @@ W: http://www.developer.ibm.com/welcome/netfinity/serveraid.html
> S: Supported
> F: drivers/scsi/ips.*
>
> +ICH LPC DRIVER
> +M: Peter Tyser <[email protected]>
> +S: Maintained
> +F: drivers/mfd/ich_lpc.c
> +F: drivers/gpio/ich_gpio.c
> +
> IDE SUBSYSTEM
> M: "David S. Miller" <[email protected]>
> L: [email protected]
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 2967002..5ae654d 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -140,6 +140,22 @@ config GPIO_SCH
> This driver can also be built as a module. If so, the module
> will be called sch-gpio.
>
> +config GPIO_ICH
> + tristate "Intel ICH GPIO"
> + depends on PCI && X86
> + select MFD_CORE
> + select LPC_ICH
> + help
> + Say yes here to support the GPIO functionality of a number of Intel
> + ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
> + ICH9, ICH10, Series 5/3400 (eg Ibex Peak), Series 6/C200 (eg
> + Cougar Point), NM10 (Tiger Point), and 3100 (Whitmore Lake).
> +
> + If unsure, say N.
> +
> + This driver can also be built as a module. If so, the module
> + will be called ich-gpio.
> +
> config GPIO_VX855
> tristate "VIA VX855/VX875 GPIO"
> depends on MFD_SUPPORT && PCI
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index b605f8e..840a654 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -39,6 +39,7 @@ obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
> obj-$(CONFIG_GPIO_WM8350) += wm8350-gpiolib.o
> obj-$(CONFIG_GPIO_WM8994) += wm8994-gpio.o
> obj-$(CONFIG_GPIO_SCH) += sch_gpio.o
> +obj-$(CONFIG_GPIO_ICH) += ich_gpio.o
> obj-$(CONFIG_MACH_U300) += gpio-u300.o
> obj-$(CONFIG_PLAT_NOMADIK) += gpio-nomadik.o
> obj-$(CONFIG_GPIO_RDC321X) += rdc321x-gpio.o
> diff --git a/drivers/gpio/ich_gpio.c b/drivers/gpio/ich_gpio.c
> new file mode 100644
> index 0000000..f7feca1
> --- /dev/null
> +++ b/drivers/gpio/ich_gpio.c
> @@ -0,0 +1,433 @@
> +/*
> + * Intel ICH6-10, Series 5 and 6 GPIO driver
> + *
> + * Copyright (C) 2010 Extreme Engineering Solutions.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/lpc_ich.h>
> +
> +#define DRV_NAME "ich_gpio"
> +
> +/* PCI config register offsets into LPC I/F - D31:F0 */
> +#define PCI_ICHX_ACPI_BAR 0x40
> +#define PCI_ICHX_GPIO_BAR 0x48
> +#define PCI_ICHX_GPIO_CTRL 0x4C
> +
> +/*
> + * GPIO register offsets in GPIO I/O space.
> + * Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and
> + * LVLx registers. Logic in the read/write functions takes a register and
> + * an absolute bit number and determines the proper register offset and bit
> + * number in that register. For example, to read the value of GPIO bit 50
> + * the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)],
> + * bit 18 (50%32).
> + */
> +enum GPIO_REG {
> + GPIO_USE_SEL = 0,
> + GPIO_IO_SEL,
> + GPIO_LVL,
> +};
> +
> +static const u8 ichx_regs[3][3] = {
> + {0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */
> + {0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */
> + {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */
> +};
> +
> +#define ICHX_GPIO_WRITE(val, reg) outl(val, (reg) + ichx_priv.gpio_base.start)
> +#define ICHX_GPIO_READ(reg) inl((reg) + ichx_priv.gpio_base.start)
> +#define ICHX_PM_WRITE(val, reg) outl(val, (reg) + ichx_priv.pm_base.start)
> +#define ICHX_PM_READ(reg) inl((reg) + ichx_priv.pm_base.start)
> +
> +/* Convenient wrapper to make our PCI ID table */
> +#define ICHX_GPIO_PCI_DEVICE(dev, data) \
> + .vendor = PCI_VENDOR_ID_INTEL, \
> + .device = dev, \
> + .subvendor = PCI_ANY_ID, \
> + .subdevice = PCI_ANY_ID, \
> + .class = 0, \
> + .class_mask = 0, \
> + .driver_data = (ulong)data
> +
Leftover define ?
> +struct ichx_desc {
> + /* Max GPIO pins the chipset can have */
> + uint ngpio;
> +
> + /* The offset of GPE0_STS in the PM IO region, 0 if unneeded */
> + uint gpe0_sts_ofs;
> +
> + /* USE_SEL is bogus on some chipsets, eg 3100 */
> + u32 use_sel_ignore[3];
> +
> + /* Some chipsets have quirks, let these use their own request/get */
> + int (*request)(struct gpio_chip *chip, unsigned offset);
> + int (*get)(struct gpio_chip *chip, unsigned offset);
> +};
> +
> +static struct {
> + spinlock_t lock;
> + struct platform_device *dev;
> + struct pci_dev *pdev;
> + struct gpio_chip chip;
> + struct resource gpio_base; /* GPIO IO base */
> + struct resource pm_base; /* Power Mangagment IO base */
> + struct ichx_desc *desc; /* Pointer to chipset-specific description */
> + u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */
> +} ichx_priv;
> +
> +static int modparam_gpiobase = -1; /* dynamic */
> +module_param_named(gpiobase, modparam_gpiobase, int, 0444);
> +MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, "
> + "which is the default.");
> +
> +static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
> +{
> + unsigned long flags;
> + u32 data;
> + int reg_nr = nr / 32;
> + int bit = nr & 0x1f;
> + int ret = 0;
> +
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + data = ICHX_GPIO_READ(ichx_regs[reg][reg_nr]);
> + data = (data & ~(1 << bit)) | (val << bit);
> + ICHX_GPIO_WRITE(data, ichx_regs[reg][reg_nr]);
> + if (verify && (data != ICHX_GPIO_READ(ichx_regs[reg][reg_nr])))
> + ret = -EPERM;
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return ret;
> +}
> +
> +static int ichx_read_bit(int reg, unsigned nr)
> +{
> + unsigned long flags;
> + u32 data;
> + int reg_nr = nr / 32;
> + int bit = nr & 0x1f;
> +
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + data = ICHX_GPIO_READ(ichx_regs[reg][reg_nr]);
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return data & (1 << bit) ? 1 : 0;
> +}
> +
> +static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
> +{
> + /*
> + * Try setting pin as an input and verify it worked since many pins
> + * are output-only.
> + */
> + if (ichx_write_bit(GPIO_IO_SEL, nr, 1, 1))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
> + int val)
> +{
> + /* Set GPIO output value. */
> + ichx_write_bit(GPIO_LVL, nr, val, 0);
> +
> + /*
> + * Try setting pin as an output and verify it worked since many pins
> + * are input-only.
> + */
> + if (ichx_write_bit(GPIO_IO_SEL, nr, 0, 1))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr)
> +{
> + return ichx_read_bit(GPIO_LVL, nr);
> +}
> +
> +static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr)
> +{
> + unsigned long flags;
> + u32 data;
> +
> + /*
> + * GPI 0 - 15 need to be read from the power management registers on
> + * a ICH6/3100 bridge.
> + */
> + if (nr < 16) {
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + /* GPI 0 - 15 are latched, write 1 to clear*/
> + ICHX_PM_WRITE(1 << (16 + nr), ichx_priv.desc->gpe0_sts_ofs);
> +
> + data = ICHX_PM_READ(ichx_priv.desc->gpe0_sts_ofs);
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return (data >> 16) & (1 << nr) ? 1 : 0;
> + } else {
> + return ichx_gpio_get(chip, nr);
> + }
> +}
> +
> +static int ichx_gpio_request(struct gpio_chip *chip, unsigned nr)
> +{
> + /*
> + * Note we assume the BIOS properly set a bridge's USE value. Some
> + * chips (eg Intel 3100) have bogus USE values though, so first see if
> + * the chipset's USE value can be trusted for this specific bit.
> + * If it can't be trusted, assume that the pin can be used as a GPIO.
> + */
> + if (ichx_priv.desc->use_sel_ignore[nr / 32] & (1 << (nr & 0x1f)))
> + return 1;
> +
> + return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
> +}
> +
> +static int ich6_gpio_request(struct gpio_chip *chip, unsigned nr)
> +{
> + /*
> + * Fixups for bits 16 and 17 are necessary on the Intel ICH6/3100
> + * bridge as they are controlled by USE register bits 0 and 1. See
> + * "Table 704 GPIO_USE_SEL1 register" in the i3100 datasheet for
> + * additional info.
> + */
> + if ((nr == 16) || (nr == 17))
> + nr -= 16;
> +
> + return ichx_gpio_request(chip, nr);
> +}
> +
> +static void ichx_gpio_set(struct gpio_chip *chip, unsigned nr, int val)
> +{
> + ichx_write_bit(GPIO_LVL, nr, val, 0);
> +}
> +
> +static void __devinit ichx_gpiolib_setup(struct gpio_chip *chip)
> +{
> + chip->owner = THIS_MODULE;
> + chip->label = DRV_NAME;
> +
> + /* Allow chip-specific overrides of request()/get() */
> + chip->request = ichx_priv.desc->request ?
> + ichx_priv.desc->request : ichx_gpio_request;
> + chip->get = ichx_priv.desc->get ?
> + ichx_priv.desc->get : ichx_gpio_get;
> +
> + chip->set = ichx_gpio_set;
> + chip->direction_input = ichx_gpio_direction_input;
> + chip->direction_output = ichx_gpio_direction_output;
> + chip->base = modparam_gpiobase;
> + chip->ngpio = ichx_priv.desc->ngpio;
> + chip->can_sleep = 0;
> + chip->dbg_show = NULL;
> +}
> +
> +/* ICH6-based, 631xesb-based */
> +static struct ichx_desc ich6_desc = {
> + /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
> + .request = ich6_gpio_request,
> + .get = ich6_gpio_get,
> +
> + /* GPIO 0-15 are read in the GPE0_STS PM register */
> + .gpe0_sts_ofs = 0x28,
> +
> + .ngpio = 50,
> +};
> +
> +/* Intel 3100 */
> +static struct ichx_desc i3100_desc = {
> + /*
> + * Bits 16,17, 20 of USE_SEL and bit 16 of USE_SEL2 always read 0 on
> + * the Intel 3100. See "Table 712. GPIO Summary Table" of 3100
> + * Datasheet for more info.
> + */
> + .use_sel_ignore = {0x00130000, 0x00010000, 0x0},
> +
> + /* The 3100 needs fixups for GPIO 0 - 17 */
> + .request = ich6_gpio_request,
> + .get = ich6_gpio_get,
> +
> + /* GPIO 0-15 are read in the GPE0_STS PM register */
> + .gpe0_sts_ofs = 0x28,
> +
> + .ngpio = 50,
> +};
> +
> +/* ICH7 and ICH8-based */
> +static struct ichx_desc ich7_desc = {
> + .ngpio = 50,
> +};
> +
> +/* ICH9-based */
> +static struct ichx_desc ich9_desc = {
> + .ngpio = 61,
> +};
> +
> +/* ICH10-based - Consumer/corporate versions have different amount of GPIO */
> +static struct ichx_desc ich10_cons_desc = {
> + .ngpio = 51,
> +};
> +static struct ichx_desc ich10_corp_desc = {
> + .ngpio = 72,
> +};
> +
> +/* Intel 5 series, 6 series, 3400 series, and C200 series */
> +static struct ichx_desc intel5_desc = {
> + .ngpio = 76,
> +};
> +
> +static int __devinit ichx_gpio_probe(struct platform_device *pdev)
> +{
> + struct resource *res_base, *res_pm;
> + size_t sz_base;
> + int id;
> + int err;
> +
> + id = pdev->id;
> + if (!id)
> + return -ENODEV;
> +
> + switch (lpc_chipset_info[id].gpio_version) {
> + case 0x401:
> + ichx_priv.desc = &i3100_desc;
> + break;
> + case 0x501:
> + ichx_priv.desc = &intel5_desc;
> + break;
> + case 0x601:
> + ichx_priv.desc = &ich6_desc;
> + break;
> + case 0x701:
> + ichx_priv.desc = &ich7_desc;
> + break;
> + case 0x801:
> + ichx_priv.desc = &ich9_desc;
> + break;
> + case 0xa01:
> + ichx_priv.desc = &ich10_corp_desc;
> + break;
> + case 0xa11:
> + ichx_priv.desc = &ich10_cons_desc;
> + break;
> + default:
> + goto base_err;
> + }
> +
> + res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
> + if (!res_base)
> + goto base_err;
> +
> + sz_base = ichx_priv.desc->ngpio > 64 ? resource_size(res_base) : 64;
> + if (!request_region(res_base->start, sz_base, pdev->name))
> + goto base_err;
> +
> + memcpy(&ichx_priv.gpio_base, res_base, sizeof(struct resource));
Would it make sense to use a pointer ?
> + ichx_priv.gpio_base.end = ichx_priv.gpio_base.end + sz_base;
> +
> + /*
> + * If necessary, determine the I/O address of ACPI/power management
> + * registers which are needed to read the the GPE0 register for GPI pins
> + * 0 - 15 on some chipsets.
> + */
> + if (!ichx_priv.desc->gpe0_sts_ofs)
> + goto init;
> +
> + res_pm = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPE0);
> + if (!res_pm) {
> + pr_warn("ACPI BAR not enumerated, GPI 0 - 15 "
> + "unusable\n");
> + goto init;
> + }
> +
> + if (!request_region(res_pm->start, resource_size(res_pm),
> + pdev->name)) {
> + pr_warn("ACPI BAR not enumerated, GPI 0 - 15 "
> + "unusable\n");
> + goto init;
> + }
Shouldn't something to happen in this case besides not copying res_pm ?
pm_base will be uninitialized in this case, yet as far as I can see you
still try to use it later on.
Also, do you need to release the regions when exiting ?
> +
> + memcpy(&ichx_priv.pm_base, res_pm, sizeof(struct resource));
> +
Can you use a pointer instead ?
> +init:
> + ichx_gpiolib_setup(&ichx_priv.chip);
> + err = gpiochip_add(&ichx_priv.chip);
> + if (err) {
> + pr_err("Failed to register GPIOs\n");
Do you need to release memory regions here ?
> + return err;
> + }
> +
> + pr_info("GPIO from %d to %d on %s\n", ichx_priv.chip.base,
> + ichx_priv.chip.base + ichx_priv.chip.ngpio - 1, DRV_NAME);
> +
> + return 0;
> +
> +base_err:
> + return -ENODEV;
> +}
> +
> +static int __devexit ichx_gpio_remove(struct platform_device *pdev)
> +{
> + int err;
> +
> + err = gpiochip_remove(&ichx_priv.chip);
> + if (err)
> + dev_err(&pdev->dev, "%s failed, %d\n",
> + "gpiochip_remove()", err);
> +
> + err = release_resource(&ichx_priv.gpio_base);
> +
Should this be release_region instead ? And how about pm_base ?
Also, this results in the first error - if there was one - being
overwritten.
> + return err;
> +}
> +
> +static struct platform_driver ichx_gpio_driver = {
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = DRV_NAME,
> + },
> + .probe = ichx_gpio_probe,
> + .remove = __devexit_p(ichx_gpio_remove),
> +};
> +
> +static int __devinit ichx_gpio_init_module(void)
> +{
> + return platform_driver_register(&ichx_gpio_driver);
> +}
> +
> +static void __devexit ichx_gpio_exit_module(void)
> +{
> + platform_driver_unregister(&ichx_gpio_driver);
> +}
> +
> +module_init(ichx_gpio_init_module);
Might be a good idea to replace module_init with subsys_initcall. Some
other drivers such as i2c-gpio may depend on this one being loaded
early. That only applies if the driver is built into the kernel, but it
seems to be quite common for other gpio drivers.
> +module_exit(ichx_gpio_exit_module);
> +
> +MODULE_AUTHOR("Peter Tyser <[email protected]>");
> +MODULE_DESCRIPTION("GPIO interface for Intel ICH series");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:ich_gpio");
> --
> 1.7.0.4
Thanks,
Guenter
> > If you want to send me your patchset directly, I can test it out on
> > some of our hardware (Ibex Peak and Cougar Point). I can work on
> > implementing some of your other suggestions as well before
> > resubmitting to the list.
> >
> Done. You should have received a series of four patches, your three
> plus one with a number of changes I made on top of your first patch.
> The last two patches are essentially yours modified to compile with
> 3.3-rc2 and to work (hopefully ;) with my patch.
>
> Thanks,
> Guenter
Guenter,
I received and applied your patches. I had to compile with ACPI
disabled because 3.3-rc2 now reports a resource conflict between the
WDAT structure (that we report for Microsoft Windows watchdog timer
support) and the TCO watchdog portion of lpc_ich:
ACPI Warning: 0x00000460-0x0000047f SystemIO conflicts with
Region \PMIO 1 (20120111/utaddress-251)
ACPI: If an ACPI driver is available for this device, you should use
it instead of the native driver
lpc_ich: probe of 0000:00:1f.0 failed with error -16
We may have to submit a driver in the future to utilize the WDAT
ACPI table, but that's another topic.
Do you think it's worth modifying lcp_ich to register all of the
resources that it can and only populate the mfd_cells that have
all of their required resources? That would prevent the entire
driver from bombing out in situations like this. I can't imagine
our BIOS is the only one implementing the WDAT table.
Anyway, after compiling without ACPI the lpc_ich driver registered
as expected and the gpio-ich and iTCO_wdt drivers detected and
configured their associated resources. I have verified that the
watchdog timer still works as expected. I need to track down some
other hardware to properly test the GPIO.
-Aaron
Hi Aaron,
On Fri, 03 Feb 2012 16:50:28 -0600 (CST), Aaron Sierra wrote:
> I received and applied your patches. I had to compile with ACPI
> disabled because 3.3-rc2 now reports a resource conflict between the
> WDAT structure (that we report for Microsoft Windows watchdog timer
> support) and the TCO watchdog portion of lpc_ich:
>
> ACPI Warning: 0x00000460-0x0000047f SystemIO conflicts with
> Region \PMIO 1 (20120111/utaddress-251)
> ACPI: If an ACPI driver is available for this device, you should use
> it instead of the native driver
> lpc_ich: probe of 0000:00:1f.0 failed with error -16
>
> We may have to submit a driver in the future to utilize the WDAT
> ACPI table, but that's another topic.
>
> Do you think it's worth modifying lcp_ich to register all of the
> resources that it can and only populate the mfd_cells that have
> all of their required resources? That would prevent the entire
> driver from bombing out in situations like this. I can't imagine
> our BIOS is the only one implementing the WDAT table.
Yes, definitely. One subdevice being unavailable shouldn't have any
impact on other subdevices. The lpc_sch driver suffers from the same
issue, I had sent a patch one 10 months ago but it was never applied.
I'll update it and resend it.
To mention another ACPI resource conflict that can happen, I see the
following on my Asus Z8NA-D6:
ACPI Warning: 0x0000000000000500-0x000000000000053f SystemIO conflicts with Region \_SI_.LEDR 1 (20120111/utaddress-251)
ACPI: If an ACPI driver is available for this device, you should use it instead of the native driver
i801_gpio: probe of 0000:00:1f.0 failed with error -16
OperationRegion (LEDR, SystemIO, 0x051A, One)
Field (LEDR, ByteAcc, NoLock, Preserve)
{
LEDY, 8
}
This is with my own GPIO driver, no MFD involved, so that's a different
problem but your driver will likely show the same. This might mean that
we have to fine-tune which I/O ports to request. In my case ACPI wants
to access only the "GPIO Blink Enable" register, which neither my
driver nor yours uses, so the conflict could be avoided.
> Anyway, after compiling without ACPI the lpc_ich driver registered
> as expected and the gpio-ich and iTCO_wdt drivers detected and
> configured their associated resources. I have verified that the
> watchdog timer still works as expected. I need to track down some
> other hardware to properly test the GPIO.
Note that for testing purpose only, it can be more convenient to boot
with acpi_enforce_resources=lax rather than rebuilding the kernel
without ACPI support.
--
Jean Delvare
On Sat, Feb 04, 2012 at 03:45:43AM -0500, Jean Delvare wrote:
> Hi Aaron,
>
> On Fri, 03 Feb 2012 16:50:28 -0600 (CST), Aaron Sierra wrote:
> > I received and applied your patches. I had to compile with ACPI
> > disabled because 3.3-rc2 now reports a resource conflict between the
> > WDAT structure (that we report for Microsoft Windows watchdog timer
> > support) and the TCO watchdog portion of lpc_ich:
> >
> > ACPI Warning: 0x00000460-0x0000047f SystemIO conflicts with
> > Region \PMIO 1 (20120111/utaddress-251)
> > ACPI: If an ACPI driver is available for this device, you should use
> > it instead of the native driver
> > lpc_ich: probe of 0000:00:1f.0 failed with error -16
> >
> > We may have to submit a driver in the future to utilize the WDAT
> > ACPI table, but that's another topic.
> >
> > Do you think it's worth modifying lcp_ich to register all of the
> > resources that it can and only populate the mfd_cells that have
> > all of their required resources? That would prevent the entire
> > driver from bombing out in situations like this. I can't imagine
> > our BIOS is the only one implementing the WDAT table.
>
> Yes, definitely. One subdevice being unavailable shouldn't have any
> impact on other subdevices. The lpc_sch driver suffers from the same
Definitely agree.
The gpio driver needs some rework in initialization and cleanup
to correctly reserve and release its io regions.
Aaron, Peter, do you have time to look into that, or do you want me
to take a stab and send you diffs ?
Thanks,
Guenter
This driver currently creates resources for use by a forthcoming ICH
chipset GPIO driver. It could be expanded to created the resources for
converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
drivers to use the mfd model.
Signed-off-by: Aaron Sierra <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
---
drivers/mfd/Kconfig | 9 +
drivers/mfd/Makefile | 1 +
drivers/mfd/lpc_ich.c | 510 +++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/lpc_ich.h | 32 +++
4 files changed, 552 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/lpc_ich.c
create mode 100644 include/linux/mfd/lpc_ich.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index cd13e9f..f581e59 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -724,6 +724,15 @@ config LPC_SCH
LPC bridge function of the Intel SCH provides support for
System Management Bus and General Purpose I/O.
+config LPC_ICH
+ tristate "Intel ICH LPC"
+ depends on PCI
+ select MFD_CORE
+ help
+ The LPC bridge function of the Intel ICH provides support for
+ many functional units. This driver provides needed support for
+ other drivers to control these functions, currently GPIO.
+
config MFD_RDC321X
tristate "Support for RDC-R321x southbridge"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index b953bab..6636c5a 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o
obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
+obj-$(CONFIG_LPC_ICH) += lpc_ich.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
new file mode 100644
index 0000000..3f159fc
--- /dev/null
+++ b/drivers/mfd/lpc_ich.c
@@ -0,0 +1,510 @@
+/*
+ * lpc_ich.c - LPC interface for Intel ICH
+ *
+ * LPC bridge function of the Intel ICH contains many other
+ * functional units, such as Interrupt controllers, Timers,
+ * Power Management, System Management, GPIO, RTC, and LPC
+ * Configuration Registers.
+ *
+ * This driver is derived from lpc_sch.
+
+ * Copyright (c) 2011 Extreme Engineering Solution, Inc.
+ * Author: Aaron Sierra <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * This driver supports the following I/O Controller hubs:
+ * (See the intel documentation on http://developer.intel.com.)
+ * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO)
+ * document number 290687-002, 298242-027: 82801BA (ICH2)
+ * document number 290733-003, 290739-013: 82801CA (ICH3-S)
+ * document number 290716-001, 290718-007: 82801CAM (ICH3-M)
+ * document number 290744-001, 290745-025: 82801DB (ICH4)
+ * document number 252337-001, 252663-008: 82801DBM (ICH4-M)
+ * document number 273599-001, 273645-002: 82801E (C-ICH)
+ * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R)
+ * document number 300641-004, 300884-013: 6300ESB
+ * document number 301473-002, 301474-026: 82801F (ICH6)
+ * document number 313082-001, 313075-006: 631xESB, 632xESB
+ * document number 307013-003, 307014-024: 82801G (ICH7)
+ * document number 322896-001, 322897-001: NM10
+ * document number 313056-003, 313057-017: 82801H (ICH8)
+ * document number 316972-004, 316973-012: 82801I (ICH9)
+ * document number 319973-002, 319974-002: 82801J (ICH10)
+ * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH)
+ * document number 320066-003, 320257-008: EP80597 (IICH)
+ * document number 324645-001, 324646-001: Cougar Point (CPT)
+ * document number TBD : Patsburg (PBG)
+ * document number TBD : DH89xxCC
+ * document number TBD : Panther Point
+ * document number TBD : Lynx Point
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/lpc_ich.h>
+
+#define ACPIBASE 0x40
+#define ACPIBASE_GPE_OFF 0x20
+#define ACPIBASE_GPE_END 0x2f
+#define ACPICTRL 0x44
+
+#define GPIOBASE 0x48
+#define GPIOCTRL 0x4C
+#define GPIOBASE_IO_SIZE 0x80
+
+static int lpc_ich_acpi_save = -1;
+static int lpc_ich_gpio_save = -1;
+
+static struct resource gpio_ich_res[] = {
+ /* BASE */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* ACPI */
+ {
+ .flags = IORESOURCE_IO,
+ },
+};
+
+enum lpc_cells {
+ LPC_GPIO = 0,
+};
+
+static struct mfd_cell lpc_ich_cells[] = {
+ [LPC_GPIO] = {
+ .name = "gpio_ich",
+ .num_resources = ARRAY_SIZE(gpio_ich_res),
+ .resources = gpio_ich_res,
+ },
+};
+
+/* chipset related info */
+enum lpc_chipsets {
+ LPC_ICH = 0, /* ICH */
+ LPC_ICH0, /* ICH0 */
+ LPC_ICH2, /* ICH2 */
+ LPC_ICH2M, /* ICH2-M */
+ LPC_ICH3, /* ICH3-S */
+ LPC_ICH3M, /* ICH3-M */
+ LPC_ICH4, /* ICH4 */
+ LPC_ICH4M, /* ICH4-M */
+ LPC_CICH, /* C-ICH */
+ LPC_ICH5, /* ICH5 & ICH5R */
+ LPC_6300ESB, /* 6300ESB */
+ LPC_ICH6, /* ICH6 & ICH6R */
+ LPC_ICH6M, /* ICH6-M */
+ LPC_ICH6W, /* ICH6W & ICH6RW */
+ LPC_631XESB, /* 631xESB/632xESB */
+ LPC_ICH7, /* ICH7 & ICH7R */
+ LPC_ICH7DH, /* ICH7DH */
+ LPC_ICH7M, /* ICH7-M & ICH7-U */
+ LPC_ICH7MDH, /* ICH7-M DH */
+ LPC_NM10, /* NM10 */
+ LPC_ICH8, /* ICH8 & ICH8R */
+ LPC_ICH8DH, /* ICH8DH */
+ LPC_ICH8DO, /* ICH8DO */
+ LPC_ICH8M, /* ICH8M */
+ LPC_ICH8ME, /* ICH8M-E */
+ LPC_ICH9, /* ICH9 */
+ LPC_ICH9R, /* ICH9R */
+ LPC_ICH9DH, /* ICH9DH */
+ LPC_ICH9DO, /* ICH9DO */
+ LPC_ICH9M, /* ICH9M */
+ LPC_ICH9ME, /* ICH9M-E */
+ LPC_ICH10, /* ICH10 */
+ LPC_ICH10R, /* ICH10R */
+ LPC_ICH10D, /* ICH10D */
+ LPC_ICH10DO, /* ICH10DO */
+ LPC_PCH, /* PCH Desktop Full Featured */
+ LPC_PCHM, /* PCH Mobile Full Featured */
+ LPC_P55, /* P55 */
+ LPC_PM55, /* PM55 */
+ LPC_H55, /* H55 */
+ LPC_QM57, /* QM57 */
+ LPC_H57, /* H57 */
+ LPC_HM55, /* HM55 */
+ LPC_Q57, /* Q57 */
+ LPC_HM57, /* HM57 */
+ LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */
+ LPC_QS57, /* QS57 */
+ LPC_3400, /* 3400 */
+ LPC_3420, /* 3420 */
+ LPC_3450, /* 3450 */
+ LPC_EP80579, /* EP80579 */
+ LPC_CPT, /* Cougar Point */
+ LPC_CPTD, /* Cougar Point Desktop */
+ LPC_CPTM, /* Cougar Point Mobile */
+ LPC_PBG, /* Patsburg */
+ LPC_DH89XXCC, /* DH89xxCC */
+ LPC_PPT, /* Panther Point */
+ LPC_LPT, /* Lynx Point */
+};
+
+struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
+ [LPC_ICH] = {"ICH", 0},
+ [LPC_ICH0] = {"ICH0", 0},
+ [LPC_ICH2] = {"ICH2", 0},
+ [LPC_ICH2M] = {"ICH2-M", 0},
+ [LPC_ICH3] = {"ICH3-S", 0},
+ [LPC_ICH3M] = {"ICH3-M", 0},
+ [LPC_ICH4] = {"ICH4", 0},
+ [LPC_ICH4M] = {"ICH4-M", 0},
+ [LPC_CICH] = {"C-ICH", 0},
+ [LPC_ICH5] = {"ICH5 or ICH5R", 0},
+ [LPC_6300ESB] = {"6300ESB", 0},
+ [LPC_ICH6] = {"ICH6 or ICH6R", 0x0601},
+ [LPC_ICH6M] = {"ICH6-M", 0x0601},
+ [LPC_ICH6W] = {"ICH6W or ICH6RW", 0x0601},
+ [LPC_631XESB] = {"631xESB/632xESB", 0x0601},
+ [LPC_ICH7] = {"ICH7 or ICH7R", 0x0701},
+ [LPC_ICH7DH] = {"ICH7DH", 0x0701},
+ [LPC_ICH7M] = {"ICH7-M or ICH7-U", 0x0701},
+ [LPC_ICH7MDH] = {"ICH7-M DH", 0x0701},
+ [LPC_NM10] = {"NM10", 0},
+ [LPC_ICH8] = {"ICH8 or ICH8R", 0x0701},
+ [LPC_ICH8DH] = {"ICH8DH", 0x0701},
+ [LPC_ICH8DO] = {"ICH8DO", 0x0701},
+ [LPC_ICH8M] = {"ICH8M", 0x0701},
+ [LPC_ICH8ME] = {"ICH8M-E", 0x0701},
+ [LPC_ICH9] = {"ICH9", 0x0801},
+ [LPC_ICH9R] = {"ICH9R", 0x0801},
+ [LPC_ICH9DH] = {"ICH9DH", 0x0801},
+ [LPC_ICH9DO] = {"ICH9DO", 0x0801},
+ [LPC_ICH9M] = {"ICH9M", 0x0801},
+ [LPC_ICH9ME] = {"ICH9M-E", 0x0801},
+ [LPC_ICH10] = {"ICH10", 0x0a11},
+ [LPC_ICH10R] = {"ICH10R", 0x0a11},
+ [LPC_ICH10D] = {"ICH10D", 0x0a01},
+ [LPC_ICH10DO] = {"ICH10DO", 0x0a01},
+ [LPC_PCH] = {"PCH Desktop Full Featured", 0x0501},
+ [LPC_PCHM] = {"PCH Mobile Full Featured", 0x0501},
+ [LPC_P55] = {"P55", 0x0501},
+ [LPC_PM55] = {"PM55", 0x0501},
+ [LPC_H55] = {"H55", 0x0501},
+ [LPC_QM57] = {"QM57", 0x0501},
+ [LPC_H57] = {"H57", 0x0501},
+ [LPC_HM55] = {"HM55", 0x0501},
+ [LPC_Q57] = {"Q57", 0x0501},
+ [LPC_HM57] = {"HM57", 0x0501},
+ [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",0x0501},
+ [LPC_QS57] = {"QS57", 0x0501},
+ [LPC_3400] = {"3400", 0x0501},
+ [LPC_3420] = {"3420", 0x0501},
+ [LPC_3450] = {"3450", 0x0501},
+ [LPC_EP80579] = {"EP80579", 0},
+ [LPC_CPT] = {"Cougar Point", 0x0501},
+ [LPC_CPTD] = {"Cougar Point Desktop", 0x0501},
+ [LPC_CPTM] = {"Cougar Point Mobile", 0x0501},
+ [LPC_PBG] = {"Patsburg", 0},
+ [LPC_DH89XXCC] = {"DH89xxCC", 0},
+ [LPC_PPT] = {"Panther Point", 0},
+ [LPC_LPT] = {"Lynx Point", 0},
+};
+
+/*
+ * This data only exists for exporting the supported PCI ids
+ * via MODULE_DEVICE_TABLE. We do not actually register a
+ * pci_driver, because the I/O Controller Hub has also other
+ * functions that probably will be registered by other drivers.
+ */
+static DEFINE_PCI_DEVICE_TABLE(lpc_ich_ids) = {
+ { PCI_VDEVICE(INTEL, 0x2410), LPC_ICH},
+ { PCI_VDEVICE(INTEL, 0x2420), LPC_ICH0},
+ { PCI_VDEVICE(INTEL, 0x2440), LPC_ICH2},
+ { PCI_VDEVICE(INTEL, 0x244c), LPC_ICH2M},
+ { PCI_VDEVICE(INTEL, 0x2480), LPC_ICH3},
+ { PCI_VDEVICE(INTEL, 0x248c), LPC_ICH3M},
+ { PCI_VDEVICE(INTEL, 0x24c0), LPC_ICH4},
+ { PCI_VDEVICE(INTEL, 0x24cc), LPC_ICH4M},
+ { PCI_VDEVICE(INTEL, 0x2450), LPC_CICH},
+ { PCI_VDEVICE(INTEL, 0x24d0), LPC_ICH5},
+ { PCI_VDEVICE(INTEL, 0x25a1), LPC_6300ESB},
+ { PCI_VDEVICE(INTEL, 0x2640), LPC_ICH6},
+ { PCI_VDEVICE(INTEL, 0x2641), LPC_ICH6M},
+ { PCI_VDEVICE(INTEL, 0x2642), LPC_ICH6W},
+ { PCI_VDEVICE(INTEL, 0x2670), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2671), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2672), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2673), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2674), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2675), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2676), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2677), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2678), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x2679), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267a), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267b), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267c), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267d), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267e), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x267f), LPC_631XESB},
+ { PCI_VDEVICE(INTEL, 0x27b8), LPC_ICH7},
+ { PCI_VDEVICE(INTEL, 0x27b0), LPC_ICH7DH},
+ { PCI_VDEVICE(INTEL, 0x27b9), LPC_ICH7M},
+ { PCI_VDEVICE(INTEL, 0x27bd), LPC_ICH7MDH},
+ { PCI_VDEVICE(INTEL, 0x27bc), LPC_NM10},
+ { PCI_VDEVICE(INTEL, 0x2810), LPC_ICH8},
+ { PCI_VDEVICE(INTEL, 0x2812), LPC_ICH8DH},
+ { PCI_VDEVICE(INTEL, 0x2814), LPC_ICH8DO},
+ { PCI_VDEVICE(INTEL, 0x2815), LPC_ICH8M},
+ { PCI_VDEVICE(INTEL, 0x2811), LPC_ICH8ME},
+ { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9},
+ { PCI_VDEVICE(INTEL, 0x2916), LPC_ICH9R},
+ { PCI_VDEVICE(INTEL, 0x2912), LPC_ICH9DH},
+ { PCI_VDEVICE(INTEL, 0x2914), LPC_ICH9DO},
+ { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M},
+ { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME},
+ { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10},
+ { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R},
+ { PCI_VDEVICE(INTEL, 0x3a1a), LPC_ICH10D},
+ { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO},
+ { PCI_VDEVICE(INTEL, 0x3b00), LPC_PCH},
+ { PCI_VDEVICE(INTEL, 0x3b01), LPC_PCHM},
+ { PCI_VDEVICE(INTEL, 0x3b02), LPC_P55},
+ { PCI_VDEVICE(INTEL, 0x3b03), LPC_PM55},
+ { PCI_VDEVICE(INTEL, 0x3b06), LPC_H55},
+ { PCI_VDEVICE(INTEL, 0x3b07), LPC_QM57},
+ { PCI_VDEVICE(INTEL, 0x3b08), LPC_H57},
+ { PCI_VDEVICE(INTEL, 0x3b09), LPC_HM55},
+ { PCI_VDEVICE(INTEL, 0x3b0a), LPC_Q57},
+ { PCI_VDEVICE(INTEL, 0x3b0b), LPC_HM57},
+ { PCI_VDEVICE(INTEL, 0x3b0d), LPC_PCHMSFF},
+ { PCI_VDEVICE(INTEL, 0x3b0f), LPC_QS57},
+ { PCI_VDEVICE(INTEL, 0x3b12), LPC_3400},
+ { PCI_VDEVICE(INTEL, 0x3b14), LPC_3420},
+ { PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
+ { PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
+ { PCI_VDEVICE(INTEL, 0x1c41), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c42), LPC_CPTD},
+ { PCI_VDEVICE(INTEL, 0x1c43), LPC_CPTM},
+ { PCI_VDEVICE(INTEL, 0x1c44), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c45), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c46), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c47), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c48), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c49), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4a), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4b), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4c), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4d), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4e), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c4f), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c50), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c51), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c52), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c53), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c54), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c55), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c56), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c57), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c58), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c59), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5a), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5b), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5c), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5d), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5e), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1c5f), LPC_CPT},
+ { PCI_VDEVICE(INTEL, 0x1d40), LPC_PBG},
+ { PCI_VDEVICE(INTEL, 0x1d41), LPC_PBG},
+ { PCI_VDEVICE(INTEL, 0x2310), LPC_DH89XXCC},
+ { PCI_VDEVICE(INTEL, 0x1e40), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e41), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e42), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e43), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e44), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e45), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e46), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e47), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e48), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e49), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4a), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4b), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4c), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4d), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4e), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e4f), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e50), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e51), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e52), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e53), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e54), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e55), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e56), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e57), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e58), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e59), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5a), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5b), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5c), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5d), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5e), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x1e5f), LPC_PPT},
+ { PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c43), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c44), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c45), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c46), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c47), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c48), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c49), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4a), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4b), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4c), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4d), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4e), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c4f), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c50), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c51), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c52), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c53), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c54), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c55), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c56), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c57), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c58), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c59), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5a), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5b), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5c), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5d), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5e), LPC_LPT},
+ { PCI_VDEVICE(INTEL, 0x8c5f), LPC_LPT},
+ { 0, }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, lpc_ich_ids);
+
+static void lpc_ich_finalize_cell(struct mfd_cell *cell,
+ const struct pci_device_id *id)
+{
+ cell->id = id->driver_data;
+ cell->platform_data = &lpc_chipset_info[id->driver_data];
+ cell->pdata_size = sizeof(struct lpc_ich_info);
+}
+
+static int __devinit lpc_ich_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ u32 base_addr_cfg;
+ u32 base_addr;
+ u8 reg_save;
+ int ret;
+ int cells = 0;
+ int acpi_conflict = 0;
+
+ /* Setup power management base register */
+ pci_read_config_dword(dev, ACPIBASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0x0000ff80;
+ if (!base_addr) {
+ dev_err(&dev->dev, "I/O space for ACPI uninitialized\n");
+ goto pm_done;
+ }
+
+ gpio_ich_res[ICH_RES_GPE0].start = base_addr + ACPIBASE_GPE_OFF;
+ gpio_ich_res[ICH_RES_GPE0].end = base_addr + ACPIBASE_GPE_END;
+ ret = acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPE0]);
+ if (ret) {
+ /* this isn't necessarily fatal for the GPIO */
+ gpio_ich_res[ICH_RES_GPE0].start = 0;
+ gpio_ich_res[ICH_RES_GPE0].end = 0;
+ acpi_conflict++;
+ }
+
+ /* Enable LPC ACPI space */
+ pci_read_config_byte(dev, ACPICTRL, ®_save);
+ pci_write_config_byte(dev, ACPICTRL, reg_save | 0x10);
+ lpc_ich_acpi_save = (int)reg_save;
+
+pm_done:
+ /* Setup GPIO base register */
+ pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0x0000ff80;
+ if (!base_addr) {
+ dev_err(&dev->dev, "I/O space for GPIO uninitialized\n");
+ /* GPIO in power-management space may still be available */
+ goto gpio_reg;
+ }
+
+ gpio_ich_res[ICH_RES_GPIO].start = base_addr;
+ gpio_ich_res[ICH_RES_GPIO].end = base_addr + GPIOBASE_IO_SIZE - 1;
+ ret = acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPIO]);
+ if (ret) {
+ /* this isn't necessarily fatal for the GPIO */
+ gpio_ich_res[ICH_RES_GPIO].start = 0;
+ gpio_ich_res[ICH_RES_GPIO].end = 0;
+ acpi_conflict++;
+ goto gpio_reg;
+ }
+
+ /* Enable LPC GPIO space */
+ pci_read_config_byte(dev, GPIOCTRL, ®_save);
+ pci_write_config_byte(dev, GPIOCTRL, reg_save | 0x10);
+ lpc_ich_gpio_save = (int)reg_save;
+
+gpio_reg:
+ lpc_ich_finalize_cell(&lpc_ich_cells[LPC_GPIO], id);
+ ret = mfd_add_devices(&dev->dev, 0, &lpc_ich_cells[LPC_GPIO],
+ 1, NULL, 0);
+ if (!ret)
+ cells++;
+
+ if (acpi_conflict)
+ dev_info(&dev->dev, "ACPI resource conflicts found; "
+ "consider using acpi_enforce_resources=lax?\n");
+
+ if (cells)
+ return 0;
+ else
+ return -ENODEV;
+}
+
+static void __devexit lpc_ich_remove(struct pci_dev *dev)
+{
+ mfd_remove_devices(&dev->dev);
+
+ pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save);
+ pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save);
+}
+
+static struct pci_driver lpc_ich_driver = {
+ .name = "lpc_ich",
+ .id_table = lpc_ich_ids,
+ .probe = lpc_ich_probe,
+ .remove = __devexit_p(lpc_ich_remove),
+};
+
+static int __init lpc_ich_init(void)
+{
+ return pci_register_driver(&lpc_ich_driver);
+}
+
+static void __exit lpc_ich_exit(void)
+{
+ pci_unregister_driver(&lpc_ich_driver);
+}
+
+module_init(lpc_ich_init);
+module_exit(lpc_ich_exit);
+
+MODULE_AUTHOR("Aaron Sierra <[email protected]>");
+MODULE_DESCRIPTION("LPC interface for Intel ICH");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
new file mode 100644
index 0000000..286c778
--- /dev/null
+++ b/include/linux/mfd/lpc_ich.h
@@ -0,0 +1,32 @@
+/*
+ * linux/drivers/mfd/lpc_ich.h
+ *
+ * Copyright (c) 2012 Extreme Engineering Solution, Inc.
+ * Author: Aaron Sierra <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef LPC_ICH_H
+#define LPC_ICH_H
+
+/* GPIO resources */
+#define ICH_RES_GPIO 0
+#define ICH_RES_GPE0 1
+
+struct lpc_ich_info {
+ char name[32];
+ unsigned int gpio_version;
+};
+
+#endif
--
1.7.0.4
This driver works on many Intel chipsets, including the ICH6, ICH7,
ICH8, ICH9, ICH10, 3100, Series 5/3400 (Ibex Peak), Series 6/C200
(Cougar Point), and NM10 (Tiger Point).
Additional Intel chipsets should be easily supported if needed, eg the
ICH1-5, EP80579, etc.
Tested on QM67 (Cougar Point), QM57 (Ibex Peak), 3100 (Whitmore Lake),
and NM10 (Tiger Point).
Signed-off-by: Aaron Sierra <[email protected]>
Signed-off-by: Peter Tyser <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
---
MAINTAINERS | 6 +
drivers/gpio/Kconfig | 13 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ich.c | 437 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 457 insertions(+), 0 deletions(-)
create mode 100644 drivers/gpio/gpio-ich.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a1fce9a..9a0b59f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3284,6 +3284,12 @@ W: http://www.developer.ibm.com/welcome/netfinity/serveraid.html
S: Supported
F: drivers/scsi/ips.*
+ICH LPC DRIVER
+M: Peter Tyser <[email protected]>
+S: Maintained
+F: drivers/mfd/lpc_ich.c
+F: drivers/gpio/gpio-ich.c
+
IDE SUBSYSTEM
M: "David S. Miller" <[email protected]>
L: [email protected]
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d0c4118..3359f1e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -178,6 +178,19 @@ config GPIO_SCH
The Intel Tunnel Creek processor has 5 GPIOs powered by the
core power rail and 9 from suspend power supply.
+config GPIO_ICH
+ tristate "Intel ICH GPIO"
+ depends on PCI && X86
+ select MFD_CORE
+ select LPC_ICH
+ help
+ Say yes here to support the GPIO functionality of a number of Intel
+ ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
+ ICH9, ICH10, Series 5/3400 (eg Ibex Peak), Series 6/C200 (eg
+ Cougar Point), NM10 (Tiger Point), and 3100 (Whitmore Lake).
+
+ If unsure, say N.
+
config GPIO_VX855
tristate "VIA VX855/VX875 GPIO"
depends on PCI
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index fa10df6..b538830 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_PLAT_SAMSUNG) += gpio-samsung.o
obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
+obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o
obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o
diff --git a/drivers/gpio/gpio-ich.c b/drivers/gpio/gpio-ich.c
new file mode 100644
index 0000000..0bf76dc
--- /dev/null
+++ b/drivers/gpio/gpio-ich.c
@@ -0,0 +1,437 @@
+/*
+ * Intel ICH6-10, Series 5 and 6 GPIO driver
+ *
+ * Copyright (C) 2010 Extreme Engineering Solutions.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/lpc_ich.h>
+
+#define DRV_NAME "gpio_ich"
+
+/* PCI config register offsets into LPC I/F - D31:F0 */
+#define PCI_ICHX_ACPI_BAR 0x40
+#define PCI_ICHX_GPIO_BAR 0x48
+#define PCI_ICHX_GPIO_CTRL 0x4C
+
+/*
+ * GPIO register offsets in GPIO I/O space.
+ * Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and
+ * LVLx registers. Logic in the read/write functions takes a register and
+ * an absolute bit number and determines the proper register offset and bit
+ * number in that register. For example, to read the value of GPIO bit 50
+ * the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)],
+ * bit 18 (50%32).
+ */
+enum GPIO_REG {
+ GPIO_USE_SEL = 0,
+ GPIO_IO_SEL,
+ GPIO_LVL,
+};
+
+static const u8 ichx_regs[3][3] = {
+ {0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */
+ {0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */
+ {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */
+};
+
+#define ICHX_WRITE(val, reg, base_res) outl(val, (reg) + (base_res)->start)
+#define ICHX_READ(reg, base_res) inl((reg) + (base_res)->start)
+
+struct ichx_desc {
+ /* Max GPIO pins the chipset can have */
+ uint ngpio;
+
+ /* The offset of GPE0_STS in the PM IO region, 0 if unneeded */
+ uint gpe0_sts_ofs;
+
+ /* USE_SEL is bogus on some chipsets, eg 3100 */
+ u32 use_sel_ignore[3];
+
+ /* Some chipsets have quirks, let these use their own request/get */
+ int (*request)(struct gpio_chip *chip, unsigned offset);
+ int (*get)(struct gpio_chip *chip, unsigned offset);
+};
+
+static struct {
+ spinlock_t lock;
+ struct platform_device *dev;
+ struct pci_dev *pdev;
+ struct gpio_chip chip;
+ struct resource *gpio_base; /* GPIO IO base */
+ struct resource *pm_base; /* Power Mangagment IO base */
+ struct ichx_desc *desc; /* Pointer to chipset-specific description */
+ u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */
+} ichx_priv;
+
+static int modparam_gpiobase = -1; /* dynamic */
+module_param_named(gpiobase, modparam_gpiobase, int, 0444);
+MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, "
+ "which is the default.");
+
+static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
+{
+ unsigned long flags;
+ u32 data, tmp;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+ int ret = 0;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
+ data = (data & ~(1 << bit)) | (val << bit);
+ ICHX_WRITE(data, ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
+ tmp = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
+ if (verify && (data != tmp))
+ ret = -EPERM;
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return ret;
+}
+
+static int ichx_read_bit(int reg, unsigned nr)
+{
+ unsigned long flags;
+ u32 data;
+ int reg_nr = nr / 32;
+ int bit = nr & 0x1f;
+
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return data & (1 << bit) ? 1 : 0;
+}
+
+static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
+{
+ /*
+ * Try setting pin as an input and verify it worked since many pins
+ * are output-only.
+ */
+ if (ichx_write_bit(GPIO_IO_SEL, nr, 1, 1))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
+ int val)
+{
+ /* Set GPIO output value. */
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+
+ /*
+ * Try setting pin as an output and verify it worked since many pins
+ * are input-only.
+ */
+ if (ichx_write_bit(GPIO_IO_SEL, nr, 0, 1))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr)
+{
+ return ichx_read_bit(GPIO_LVL, nr);
+}
+
+static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr)
+{
+ unsigned long flags;
+ u32 data;
+
+ /*
+ * GPI 0 - 15 need to be read from the power management registers on
+ * a ICH6/3100 bridge.
+ */
+ if (nr < 16 && ichx_priv.pm_base) {
+ spin_lock_irqsave(&ichx_priv.lock, flags);
+
+ /* GPI 0 - 15 are latched, write 1 to clear*/
+ ICHX_WRITE(1 << (16 + nr), ichx_priv.desc->gpe0_sts_ofs,
+ ichx_priv.pm_base);
+ data = ICHX_READ(ichx_priv.desc->gpe0_sts_ofs,
+ ichx_priv.pm_base);
+
+ spin_unlock_irqrestore(&ichx_priv.lock, flags);
+
+ return (data >> 16) & (1 << nr) ? 1 : 0;
+ } else {
+ return ichx_gpio_get(chip, nr);
+ }
+}
+
+static int ichx_gpio_request(struct gpio_chip *chip, unsigned nr)
+{
+ /*
+ * Note we assume the BIOS properly set a bridge's USE value. Some
+ * chips (eg Intel 3100) have bogus USE values though, so first see if
+ * the chipset's USE value can be trusted for this specific bit.
+ * If it can't be trusted, assume that the pin can be used as a GPIO.
+ */
+ if (ichx_priv.desc->use_sel_ignore[nr / 32] & (1 << (nr & 0x1f)))
+ return 1;
+
+ return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
+}
+
+static int ich6_gpio_request(struct gpio_chip *chip, unsigned nr)
+{
+ /*
+ * Fixups for bits 16 and 17 are necessary on the Intel ICH6/3100
+ * bridge as they are controlled by USE register bits 0 and 1. See
+ * "Table 704 GPIO_USE_SEL1 register" in the i3100 datasheet for
+ * additional info.
+ */
+ if ((nr == 16) || (nr == 17))
+ nr -= 16;
+
+ return ichx_gpio_request(chip, nr);
+}
+
+static void ichx_gpio_set(struct gpio_chip *chip, unsigned nr, int val)
+{
+ ichx_write_bit(GPIO_LVL, nr, val, 0);
+}
+
+static void __devinit ichx_gpiolib_setup(struct gpio_chip *chip)
+{
+ chip->owner = THIS_MODULE;
+ chip->label = DRV_NAME;
+
+ /* Allow chip-specific overrides of request()/get() */
+ chip->request = ichx_priv.desc->request ?
+ ichx_priv.desc->request : ichx_gpio_request;
+ chip->get = ichx_priv.desc->get ?
+ ichx_priv.desc->get : ichx_gpio_get;
+
+ chip->set = ichx_gpio_set;
+ chip->direction_input = ichx_gpio_direction_input;
+ chip->direction_output = ichx_gpio_direction_output;
+ chip->base = modparam_gpiobase;
+ chip->ngpio = ichx_priv.desc->ngpio;
+ chip->can_sleep = 0;
+ chip->dbg_show = NULL;
+}
+
+/* ICH6-based, 631xesb-based */
+static struct ichx_desc ich6_desc = {
+ /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .gpe0_sts_ofs = 0x28,
+
+ .ngpio = 50,
+};
+
+/* Intel 3100 */
+static struct ichx_desc i3100_desc = {
+ /*
+ * Bits 16,17, 20 of USE_SEL and bit 16 of USE_SEL2 always read 0 on
+ * the Intel 3100. See "Table 712. GPIO Summary Table" of 3100
+ * Datasheet for more info.
+ */
+ .use_sel_ignore = {0x00130000, 0x00010000, 0x0},
+
+ /* The 3100 needs fixups for GPIO 0 - 17 */
+ .request = ich6_gpio_request,
+ .get = ich6_gpio_get,
+
+ /* GPIO 0-15 are read in the GPE0_STS PM register */
+ .gpe0_sts_ofs = 0x28,
+
+ .ngpio = 50,
+};
+
+/* ICH7 and ICH8-based */
+static struct ichx_desc ich7_desc = {
+ .ngpio = 50,
+};
+
+/* ICH9-based */
+static struct ichx_desc ich9_desc = {
+ .ngpio = 61,
+};
+
+/* ICH10-based - Consumer/corporate versions have different amount of GPIO */
+static struct ichx_desc ich10_cons_desc = {
+ .ngpio = 61,
+};
+static struct ichx_desc ich10_corp_desc = {
+ .ngpio = 72,
+};
+
+/* Intel 5 series, 6 series, 3400 series, and C200 series */
+static struct ichx_desc intel5_desc = {
+ .ngpio = 76,
+};
+
+static int __devinit ichx_gpio_probe(struct platform_device *pdev)
+{
+ struct resource *res_base, *res_pm;
+ int err;
+ struct lpc_ich_info *ich_info = pdev->dev.platform_data;
+
+ if (!ich_info)
+ return -ENODEV;
+
+ switch (ich_info-> gpio_version) {
+ case 0x401:
+ ichx_priv.desc = &i3100_desc;
+ break;
+ case 0x501:
+ ichx_priv.desc = &intel5_desc;
+ break;
+ case 0x601:
+ ichx_priv.desc = &ich6_desc;
+ break;
+ case 0x701:
+ ichx_priv.desc = &ich7_desc;
+ break;
+ case 0x801:
+ ichx_priv.desc = &ich9_desc;
+ break;
+ case 0xa01:
+ ichx_priv.desc = &ich10_corp_desc;
+ break;
+ case 0xa11:
+ ichx_priv.desc = &ich10_cons_desc;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
+ if (!res_base || !res_base->start || !res_base->end)
+ return -ENODEV;
+
+ /*
+ * The GPIO resource size reported by lpc_ich is the largest size
+ * for any of the supported chipsets. Older devices provide fewer
+ * GPIO and have a smaller resource size.
+ */
+ if (ichx_priv.desc->ngpio > 64)
+ res_base->end = res_base->start + 64 - 1;
+
+ if (!request_region(res_base->start, resource_size(res_base),
+ pdev->name))
+ return -EBUSY;
+
+ ichx_priv.gpio_base = res_base;
+
+ /*
+ * If necessary, determine the I/O address of ACPI/power management
+ * registers which are needed to read the the GPE0 register for GPI pins
+ * 0 - 15 on some chipsets.
+ */
+ if (!ichx_priv.desc->gpe0_sts_ofs)
+ goto init;
+
+ res_pm = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPE0);
+ if (!res_pm || !res_pm->start || !res_pm->end) {
+ pr_warn("ACPI BAR is unavailable, GPI 0 - 15 unavailable\n");
+ goto init;
+ }
+
+ if (!request_region(res_pm->start, resource_size(res_pm),
+ pdev->name)) {
+ pr_warn("ACPI BAR is busy, GPI 0 - 15 unavailable\n");
+ goto init;
+ }
+
+ ichx_priv.pm_base = res_pm;
+
+init:
+ ichx_gpiolib_setup(&ichx_priv.chip);
+ err = gpiochip_add(&ichx_priv.chip);
+ if (err) {
+ pr_err("Failed to register GPIOs\n");
+ goto add_err;
+ }
+
+ pr_info("GPIO from %d to %d on %s\n", ichx_priv.chip.base,
+ ichx_priv.chip.base + ichx_priv.chip.ngpio - 1, DRV_NAME);
+
+ return 0;
+
+add_err:
+ release_region(ichx_priv.gpio_base->start,
+ resource_size(ichx_priv.gpio_base));
+ if (ichx_priv.pm_base)
+ release_region(ichx_priv.pm_base->start,
+ resource_size(ichx_priv.pm_base));
+ return err;
+}
+
+static int __devexit ichx_gpio_remove(struct platform_device *pdev)
+{
+ int err;
+
+ err = gpiochip_remove(&ichx_priv.chip);
+ if (err) {
+ dev_err(&pdev->dev, "%s failed, %d\n",
+ "gpiochip_remove()", err);
+ return err;
+ }
+
+ release_region(ichx_priv.gpio_base->start,
+ resource_size(ichx_priv.gpio_base));
+ if (ichx_priv.pm_base)
+ release_region(ichx_priv.pm_base->start,
+ resource_size(ichx_priv.pm_base));
+
+ return 0;
+}
+
+static struct platform_driver ichx_gpio_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ },
+ .probe = ichx_gpio_probe,
+ .remove = __devexit_p(ichx_gpio_remove),
+};
+
+static int __devinit ichx_gpio_init_module(void)
+{
+ return platform_driver_register(&ichx_gpio_driver);
+}
+
+static void __devexit ichx_gpio_exit_module(void)
+{
+ platform_driver_unregister(&ichx_gpio_driver);
+}
+
+subsys_initcall(ichx_gpio_init_module);
+module_exit(ichx_gpio_exit_module);
+
+MODULE_AUTHOR("Peter Tyser <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for Intel ICH series");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:"DRV_NAME);
--
1.7.0.4
This patch converts the iTCO_wdt driver to use the multi-function device
driver model. It uses resources discovered by the lpc_ich driver, so that
it no longer does its own PCI scanning.
The driver has also been modernized to use pr_info and the like.
Signed-off-by: Aaron Sierra <[email protected]>
Signed-off-by: Guenter Roeck <[email protected]>
---
drivers/mfd/Kconfig | 3 +-
drivers/mfd/lpc_ich.c | 199 +++++++++----
drivers/watchdog/Kconfig | 1 +
drivers/watchdog/iTCO_vendor.h | 6 +-
drivers/watchdog/iTCO_vendor_support.c | 43 ++--
drivers/watchdog/iTCO_wdt.c | 514 ++++++--------------------------
include/linux/mfd/lpc_ich.h | 7 +
7 files changed, 257 insertions(+), 516 deletions(-)
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f581e59..428e0a2 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -731,7 +731,8 @@ config LPC_ICH
help
The LPC bridge function of the Intel ICH provides support for
many functional units. This driver provides needed support for
- other drivers to control these functions, currently GPIO.
+ other drivers to control these functions, currently GPIO and
+ watchdog.
config MFD_RDC321X
tristate "Support for RDC-R321x southbridge"
diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
index 3f159fc..eb37b05 100644
--- a/drivers/mfd/lpc_ich.c
+++ b/drivers/mfd/lpc_ich.c
@@ -63,15 +63,43 @@
#define ACPIBASE 0x40
#define ACPIBASE_GPE_OFF 0x20
#define ACPIBASE_GPE_END 0x2f
+#define ACPIBASE_SMI_OFF 0x30
+#define ACPIBASE_SMI_END 0x33
+#define ACPIBASE_TCO_OFF 0x60
+#define ACPIBASE_TCO_END 0x7f
#define ACPICTRL 0x44
+#define ACPIBASE_GCS_OFF 0x3410
+#define ACPIBASE_GCS_END 0x3414
+
#define GPIOBASE 0x48
#define GPIOCTRL 0x4C
#define GPIOBASE_IO_SIZE 0x80
+#define RCBABASE 0xf0
+
+#define wdt_io_res(i) wdt_res(0, i)
+#define wdt_mem_res(i) wdt_res(ICH_RES_MEM_OFF, i)
+#define wdt_res(b, i) (&wdt_ich_res[(b) + (i)])
+
static int lpc_ich_acpi_save = -1;
static int lpc_ich_gpio_save = -1;
+static struct resource wdt_ich_res[] = {
+ /* TCO */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* SMI */
+ {
+ .flags = IORESOURCE_IO,
+ },
+ /* GCS */
+ {
+ .flags = IORESOURCE_MEM,
+ },
+};
+
static struct resource gpio_ich_res[] = {
/* BASE */
{
@@ -84,10 +112,16 @@ static struct resource gpio_ich_res[] = {
};
enum lpc_cells {
- LPC_GPIO = 0,
+ LPC_WDT = 0,
+ LPC_GPIO,
};
static struct mfd_cell lpc_ich_cells[] = {
+ [LPC_WDT] = {
+ .name = "iTCO_wdt",
+ .num_resources = ARRAY_SIZE(wdt_ich_res),
+ .resources = wdt_ich_res,
+ },
[LPC_GPIO] = {
.name = "gpio_ich",
.num_resources = ARRAY_SIZE(gpio_ich_res),
@@ -158,64 +192,64 @@ enum lpc_chipsets {
};
struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
- [LPC_ICH] = {"ICH", 0},
- [LPC_ICH0] = {"ICH0", 0},
- [LPC_ICH2] = {"ICH2", 0},
- [LPC_ICH2M] = {"ICH2-M", 0},
- [LPC_ICH3] = {"ICH3-S", 0},
- [LPC_ICH3M] = {"ICH3-M", 0},
- [LPC_ICH4] = {"ICH4", 0},
- [LPC_ICH4M] = {"ICH4-M", 0},
- [LPC_CICH] = {"C-ICH", 0},
- [LPC_ICH5] = {"ICH5 or ICH5R", 0},
- [LPC_6300ESB] = {"6300ESB", 0},
- [LPC_ICH6] = {"ICH6 or ICH6R", 0x0601},
- [LPC_ICH6M] = {"ICH6-M", 0x0601},
- [LPC_ICH6W] = {"ICH6W or ICH6RW", 0x0601},
- [LPC_631XESB] = {"631xESB/632xESB", 0x0601},
- [LPC_ICH7] = {"ICH7 or ICH7R", 0x0701},
- [LPC_ICH7DH] = {"ICH7DH", 0x0701},
- [LPC_ICH7M] = {"ICH7-M or ICH7-U", 0x0701},
- [LPC_ICH7MDH] = {"ICH7-M DH", 0x0701},
- [LPC_NM10] = {"NM10", 0},
- [LPC_ICH8] = {"ICH8 or ICH8R", 0x0701},
- [LPC_ICH8DH] = {"ICH8DH", 0x0701},
- [LPC_ICH8DO] = {"ICH8DO", 0x0701},
- [LPC_ICH8M] = {"ICH8M", 0x0701},
- [LPC_ICH8ME] = {"ICH8M-E", 0x0701},
- [LPC_ICH9] = {"ICH9", 0x0801},
- [LPC_ICH9R] = {"ICH9R", 0x0801},
- [LPC_ICH9DH] = {"ICH9DH", 0x0801},
- [LPC_ICH9DO] = {"ICH9DO", 0x0801},
- [LPC_ICH9M] = {"ICH9M", 0x0801},
- [LPC_ICH9ME] = {"ICH9M-E", 0x0801},
- [LPC_ICH10] = {"ICH10", 0x0a11},
- [LPC_ICH10R] = {"ICH10R", 0x0a11},
- [LPC_ICH10D] = {"ICH10D", 0x0a01},
- [LPC_ICH10DO] = {"ICH10DO", 0x0a01},
- [LPC_PCH] = {"PCH Desktop Full Featured", 0x0501},
- [LPC_PCHM] = {"PCH Mobile Full Featured", 0x0501},
- [LPC_P55] = {"P55", 0x0501},
- [LPC_PM55] = {"PM55", 0x0501},
- [LPC_H55] = {"H55", 0x0501},
- [LPC_QM57] = {"QM57", 0x0501},
- [LPC_H57] = {"H57", 0x0501},
- [LPC_HM55] = {"HM55", 0x0501},
- [LPC_Q57] = {"Q57", 0x0501},
- [LPC_HM57] = {"HM57", 0x0501},
- [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",0x0501},
- [LPC_QS57] = {"QS57", 0x0501},
- [LPC_3400] = {"3400", 0x0501},
- [LPC_3420] = {"3420", 0x0501},
- [LPC_3450] = {"3450", 0x0501},
- [LPC_EP80579] = {"EP80579", 0},
- [LPC_CPT] = {"Cougar Point", 0x0501},
- [LPC_CPTD] = {"Cougar Point Desktop", 0x0501},
- [LPC_CPTM] = {"Cougar Point Mobile", 0x0501},
- [LPC_PBG] = {"Patsburg", 0},
- [LPC_DH89XXCC] = {"DH89xxCC", 0},
- [LPC_PPT] = {"Panther Point", 0},
- [LPC_LPT] = {"Lynx Point", 0},
+ [LPC_ICH] = {"ICH", 1, 0},
+ [LPC_ICH0] = {"ICH0", 1, 0},
+ [LPC_ICH2] = {"ICH2", 1, 0},
+ [LPC_ICH2M] = {"ICH2-M", 1, 0},
+ [LPC_ICH3] = {"ICH3-S", 1, 0},
+ [LPC_ICH3M] = {"ICH3-M", 1, 0},
+ [LPC_ICH4] = {"ICH4", 1, 0},
+ [LPC_ICH4M] = {"ICH4-M", 1, 0},
+ [LPC_CICH] = {"C-ICH", 1, 0},
+ [LPC_ICH5] = {"ICH5 or ICH5R", 1, 0},
+ [LPC_6300ESB] = {"6300ESB", 1, 0},
+ [LPC_ICH6] = {"ICH6 or ICH6R", 2, 0x0601},
+ [LPC_ICH6M] = {"ICH6-M", 2, 0x0601},
+ [LPC_ICH6W] = {"ICH6W or ICH6RW", 2, 0x0601},
+ [LPC_631XESB] = {"631xESB/632xESB", 2, 0x0601},
+ [LPC_ICH7] = {"ICH7 or ICH7R", 2, 0x0701},
+ [LPC_ICH7DH] = {"ICH7DH", 2, 0x0701},
+ [LPC_ICH7M] = {"ICH7-M or ICH7-U", 2, 0x0701},
+ [LPC_ICH7MDH] = {"ICH7-M DH", 2, 0x0701},
+ [LPC_NM10] = {"NM10", 2, 0},
+ [LPC_ICH8] = {"ICH8 or ICH8R", 2, 0x0701},
+ [LPC_ICH8DH] = {"ICH8DH", 2, 0x0701},
+ [LPC_ICH8DO] = {"ICH8DO", 2, 0x0701},
+ [LPC_ICH8M] = {"ICH8M", 2, 0x0701},
+ [LPC_ICH8ME] = {"ICH8M-E", 2, 0x0701},
+ [LPC_ICH9] = {"ICH9", 2, 0x0801},
+ [LPC_ICH9R] = {"ICH9R", 2, 0x0801},
+ [LPC_ICH9DH] = {"ICH9DH", 2, 0x0801},
+ [LPC_ICH9DO] = {"ICH9DO", 2, 0x0801},
+ [LPC_ICH9M] = {"ICH9M", 2, 0x0801},
+ [LPC_ICH9ME] = {"ICH9M-E", 2, 0x0801},
+ [LPC_ICH10] = {"ICH10", 2, 0x0a11},
+ [LPC_ICH10R] = {"ICH10R", 2, 0x0a11},
+ [LPC_ICH10D] = {"ICH10D", 2, 0x0a01},
+ [LPC_ICH10DO] = {"ICH10DO", 2, 0x0a01},
+ [LPC_PCH] = {"PCH Desktop Full Featured", 2, 0x0501},
+ [LPC_PCHM] = {"PCH Mobile Full Featured", 2, 0x0501},
+ [LPC_P55] = {"P55", 2, 0x0501},
+ [LPC_PM55] = {"PM55", 2, 0x0501},
+ [LPC_H55] = {"H55", 2, 0x0501},
+ [LPC_QM57] = {"QM57", 2, 0x0501},
+ [LPC_H57] = {"H57", 2, 0x0501},
+ [LPC_HM55] = {"HM55", 2, 0x0501},
+ [LPC_Q57] = {"Q57", 2, 0x0501},
+ [LPC_HM57] = {"HM57", 2, 0x0501},
+ [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",2, 0x0501},
+ [LPC_QS57] = {"QS57", 2, 0x0501},
+ [LPC_3400] = {"3400", 2, 0x0501},
+ [LPC_3420] = {"3420", 2, 0x0501},
+ [LPC_3450] = {"3450", 2, 0x0501},
+ [LPC_EP80579] = {"EP80579", 2, 0},
+ [LPC_CPT] = {"Cougar Point", 2, 0x0501},
+ [LPC_CPTD] = {"Cougar Point Desktop", 2, 0x0501},
+ [LPC_CPTM] = {"Cougar Point Mobile", 2, 0x0501},
+ [LPC_PBG] = {"Patsburg", 2, 0},
+ [LPC_DH89XXCC] = {"DH89xxCC", 2, 0},
+ [LPC_PPT] = {"Panther Point", 2, 0},
+ [LPC_LPT] = {"Lynx Point", 2, 0},
};
/*
@@ -429,11 +463,52 @@ static int __devinit lpc_ich_probe(struct pci_dev *dev,
acpi_conflict++;
}
+ wdt_io_res(ICH_RES_IO_TCO)->start = base_addr + ACPIBASE_TCO_OFF;
+ wdt_io_res(ICH_RES_IO_TCO)->end = base_addr + ACPIBASE_TCO_END;
+ ret = acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_TCO));
+ if (ret) {
+ acpi_conflict++;
+ goto pm_done;
+ }
+
+ wdt_io_res(ICH_RES_IO_SMI)->start = base_addr + ACPIBASE_SMI_OFF;
+ wdt_io_res(ICH_RES_IO_SMI)->end = base_addr + ACPIBASE_SMI_END;
+ ret = acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_SMI));
+ if (ret) {
+ acpi_conflict++;
+ goto pm_done;
+ }
+
/* Enable LPC ACPI space */
pci_read_config_byte(dev, ACPICTRL, ®_save);
pci_write_config_byte(dev, ACPICTRL, reg_save | 0x10);
lpc_ich_acpi_save = (int)reg_save;
+ /*
+ * Get the Memory-Mapped GCS register. To get access to it
+ * we have to read RCBA from PCI Config space 0xf0 and use
+ * it as base. GCS = RCBA + ICH6_GCS(0x3410).
+ */
+ if (lpc_chipset_info[id->driver_data].iTCO_version == 2) {
+ pci_read_config_dword(dev, RCBABASE, &base_addr_cfg);
+ base_addr = base_addr_cfg & 0xffffc000;
+ if (base_addr_cfg & 1) {
+ wdt_mem_res(ICH_RES_MEM_GCS)->start = base_addr +
+ ACPIBASE_GCS_OFF;
+ wdt_mem_res(ICH_RES_MEM_GCS)->end = base_addr +
+ ACPIBASE_GCS_END;
+ } else {
+ pr_err("RCBA is disabled by hardware/BIOS, "
+ "device disabled\n");
+ }
+ }
+
+ lpc_ich_finalize_cell(&lpc_ich_cells[LPC_WDT], id);
+ ret = mfd_add_devices(&dev->dev, 0, &lpc_ich_cells[LPC_WDT],
+ 1, NULL, 0);
+ if (!ret)
+ cells++;
+
pm_done:
/* Setup GPIO base register */
pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
@@ -481,8 +556,10 @@ static void __devexit lpc_ich_remove(struct pci_dev *dev)
{
mfd_remove_devices(&dev->dev);
- pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save);
- pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save);
+ if (lpc_ich_gpio_save > 0)
+ pci_write_config_byte(dev, GPIOCTRL, (u8)lpc_ich_gpio_save);
+ if (lpc_ich_acpi_save > 0)
+ pci_write_config_byte(dev, ACPICTRL, (u8)lpc_ich_acpi_save);
}
static struct pci_driver lpc_ich_driver = {
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 877b107..c3a4d7f 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -557,6 +557,7 @@ config INTEL_SCU_WATCHDOG
config ITCO_WDT
tristate "Intel TCO Timer/Watchdog"
depends on (X86 || IA64) && PCI
+ select LPC_ICH
---help---
Hardware driver for the intel TCO timer based watchdog devices.
These drivers are included in the Intel 82801 I/O Controller
diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h
index 9e27e64..3c57b45 100644
--- a/drivers/watchdog/iTCO_vendor.h
+++ b/drivers/watchdog/iTCO_vendor.h
@@ -1,8 +1,8 @@
/* iTCO Vendor Specific Support hooks */
#ifdef CONFIG_ITCO_VENDOR_SUPPORT
-extern void iTCO_vendor_pre_start(unsigned long, unsigned int);
-extern void iTCO_vendor_pre_stop(unsigned long);
-extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int);
+extern void iTCO_vendor_pre_start(struct resource *, unsigned int);
+extern void iTCO_vendor_pre_stop(struct resource *);
+extern void iTCO_vendor_pre_keepalive(struct resource *, unsigned int);
extern void iTCO_vendor_pre_set_heartbeat(unsigned int);
extern int iTCO_vendor_check_noreboot_on(void);
#else
diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c
index 481d1ad..3b80d6f 100644
--- a/drivers/watchdog/iTCO_vendor_support.c
+++ b/drivers/watchdog/iTCO_vendor_support.c
@@ -34,11 +34,6 @@
#include "iTCO_vendor.h"
-/* iTCO defines */
-#define SMI_EN (acpibase + 0x30) /* SMI Control and Enable Register */
-#define TCOBASE (acpibase + 0x60) /* TCO base address */
-#define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */
-
/* List of vendor support modes */
/* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */
#define SUPERMICRO_OLD_BOARD 1
@@ -81,24 +76,24 @@ MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default="
* 20.6 seconds.
*/
-static void supermicro_old_pre_start(unsigned long acpibase)
+static void supermicro_old_pre_start(struct resource *smires)
{
unsigned long val32;
/* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
- outl(val32, SMI_EN); /* Needed to activate watchdog */
+ outl(val32, smires->start); /* Needed to activate watchdog */
}
-static void supermicro_old_pre_stop(unsigned long acpibase)
+static void supermicro_old_pre_stop(struct resource *smires)
{
unsigned long val32;
/* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
val32 |= 0x00002000; /* Turn on SMI clearing watchdog */
- outl(val32, SMI_EN); /* Needed to deactivate watchdog */
+ outl(val32, smires->start); /* Needed to deactivate watchdog */
}
/*
@@ -269,66 +264,66 @@ static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat)
* Don't use this fix if you don't need to!!!
*/
-static void broken_bios_start(unsigned long acpibase)
+static void broken_bios_start(struct resource *smires)
{
unsigned long val32;
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
/* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI#
Bit 0: GBL_SMI_EN -> 0 = No SMI# will be generated by ICH. */
val32 &= 0xffffdffe;
- outl(val32, SMI_EN);
+ outl(val32, smires->start);
}
-static void broken_bios_stop(unsigned long acpibase)
+static void broken_bios_stop(struct resource *smires)
{
unsigned long val32;
- val32 = inl(SMI_EN);
+ val32 = inl(smires->start);
/* Bit 13: TCO_EN -> 1 = Enables TCO logic generating an SMI#
Bit 0: GBL_SMI_EN -> 1 = Turn global SMI on again. */
val32 |= 0x00002001;
- outl(val32, SMI_EN);
+ outl(val32, smires->start);
}
/*
* Generic Support Functions
*/
-void iTCO_vendor_pre_start(unsigned long acpibase,
+void iTCO_vendor_pre_start(struct resource *smires,
unsigned int heartbeat)
{
switch (vendorsupport) {
case SUPERMICRO_OLD_BOARD:
- supermicro_old_pre_start(acpibase);
+ supermicro_old_pre_start(smires);
break;
case SUPERMICRO_NEW_BOARD:
supermicro_new_pre_start(heartbeat);
break;
case BROKEN_BIOS:
- broken_bios_start(acpibase);
+ broken_bios_start(smires);
break;
}
}
EXPORT_SYMBOL(iTCO_vendor_pre_start);
-void iTCO_vendor_pre_stop(unsigned long acpibase)
+void iTCO_vendor_pre_stop(struct resource *smires)
{
switch (vendorsupport) {
case SUPERMICRO_OLD_BOARD:
- supermicro_old_pre_stop(acpibase);
+ supermicro_old_pre_stop(smires);
break;
case SUPERMICRO_NEW_BOARD:
supermicro_new_pre_stop();
break;
case BROKEN_BIOS:
- broken_bios_stop(acpibase);
+ broken_bios_stop(smires);
break;
}
}
EXPORT_SYMBOL(iTCO_vendor_pre_stop);
-void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat)
+void iTCO_vendor_pre_keepalive(struct resource *smires, unsigned int heartbeat)
{
if (vendorsupport == SUPERMICRO_NEW_BOARD)
supermicro_new_pre_set_heartbeat(heartbeat);
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
index bdf401b..7a413f3 100644
--- a/drivers/watchdog/iTCO_wdt.c
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -46,7 +46,7 @@
/* Module and version information */
#define DRV_NAME "iTCO_wdt"
#define DRV_VERSION "1.07"
-#define PFX DRV_NAME ": "
+#define pr_fmt(fmt) DRV_NAME ": " fmt
/* Includes */
#include <linux/module.h> /* For module specific items */
@@ -65,316 +65,16 @@
#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <linux/io.h> /* For inb/outb/... */
+#include <linux/mfd/core.h>
+#include <linux/mfd/lpc_ich.h>
#include "iTCO_vendor.h"
-/* TCO related info */
-enum iTCO_chipsets {
- TCO_ICH = 0, /* ICH */
- TCO_ICH0, /* ICH0 */
- TCO_ICH2, /* ICH2 */
- TCO_ICH2M, /* ICH2-M */
- TCO_ICH3, /* ICH3-S */
- TCO_ICH3M, /* ICH3-M */
- TCO_ICH4, /* ICH4 */
- TCO_ICH4M, /* ICH4-M */
- TCO_CICH, /* C-ICH */
- TCO_ICH5, /* ICH5 & ICH5R */
- TCO_6300ESB, /* 6300ESB */
- TCO_ICH6, /* ICH6 & ICH6R */
- TCO_ICH6M, /* ICH6-M */
- TCO_ICH6W, /* ICH6W & ICH6RW */
- TCO_631XESB, /* 631xESB/632xESB */
- TCO_ICH7, /* ICH7 & ICH7R */
- TCO_ICH7DH, /* ICH7DH */
- TCO_ICH7M, /* ICH7-M & ICH7-U */
- TCO_ICH7MDH, /* ICH7-M DH */
- TCO_NM10, /* NM10 */
- TCO_ICH8, /* ICH8 & ICH8R */
- TCO_ICH8DH, /* ICH8DH */
- TCO_ICH8DO, /* ICH8DO */
- TCO_ICH8M, /* ICH8M */
- TCO_ICH8ME, /* ICH8M-E */
- TCO_ICH9, /* ICH9 */
- TCO_ICH9R, /* ICH9R */
- TCO_ICH9DH, /* ICH9DH */
- TCO_ICH9DO, /* ICH9DO */
- TCO_ICH9M, /* ICH9M */
- TCO_ICH9ME, /* ICH9M-E */
- TCO_ICH10, /* ICH10 */
- TCO_ICH10R, /* ICH10R */
- TCO_ICH10D, /* ICH10D */
- TCO_ICH10DO, /* ICH10DO */
- TCO_PCH, /* PCH Desktop Full Featured */
- TCO_PCHM, /* PCH Mobile Full Featured */
- TCO_P55, /* P55 */
- TCO_PM55, /* PM55 */
- TCO_H55, /* H55 */
- TCO_QM57, /* QM57 */
- TCO_H57, /* H57 */
- TCO_HM55, /* HM55 */
- TCO_Q57, /* Q57 */
- TCO_HM57, /* HM57 */
- TCO_PCHMSFF, /* PCH Mobile SFF Full Featured */
- TCO_QS57, /* QS57 */
- TCO_3400, /* 3400 */
- TCO_3420, /* 3420 */
- TCO_3450, /* 3450 */
- TCO_EP80579, /* EP80579 */
- TCO_CPT, /* Cougar Point */
- TCO_CPTD, /* Cougar Point Desktop */
- TCO_CPTM, /* Cougar Point Mobile */
- TCO_PBG, /* Patsburg */
- TCO_DH89XXCC, /* DH89xxCC */
- TCO_PPT, /* Panther Point */
- TCO_LPT, /* Lynx Point */
-};
-
-static struct {
- char *name;
- unsigned int iTCO_version;
-} iTCO_chipset_info[] __devinitdata = {
- {"ICH", 1},
- {"ICH0", 1},
- {"ICH2", 1},
- {"ICH2-M", 1},
- {"ICH3-S", 1},
- {"ICH3-M", 1},
- {"ICH4", 1},
- {"ICH4-M", 1},
- {"C-ICH", 1},
- {"ICH5 or ICH5R", 1},
- {"6300ESB", 1},
- {"ICH6 or ICH6R", 2},
- {"ICH6-M", 2},
- {"ICH6W or ICH6RW", 2},
- {"631xESB/632xESB", 2},
- {"ICH7 or ICH7R", 2},
- {"ICH7DH", 2},
- {"ICH7-M or ICH7-U", 2},
- {"ICH7-M DH", 2},
- {"NM10", 2},
- {"ICH8 or ICH8R", 2},
- {"ICH8DH", 2},
- {"ICH8DO", 2},
- {"ICH8M", 2},
- {"ICH8M-E", 2},
- {"ICH9", 2},
- {"ICH9R", 2},
- {"ICH9DH", 2},
- {"ICH9DO", 2},
- {"ICH9M", 2},
- {"ICH9M-E", 2},
- {"ICH10", 2},
- {"ICH10R", 2},
- {"ICH10D", 2},
- {"ICH10DO", 2},
- {"PCH Desktop Full Featured", 2},
- {"PCH Mobile Full Featured", 2},
- {"P55", 2},
- {"PM55", 2},
- {"H55", 2},
- {"QM57", 2},
- {"H57", 2},
- {"HM55", 2},
- {"Q57", 2},
- {"HM57", 2},
- {"PCH Mobile SFF Full Featured", 2},
- {"QS57", 2},
- {"3400", 2},
- {"3420", 2},
- {"3450", 2},
- {"EP80579", 2},
- {"Cougar Point", 2},
- {"Cougar Point Desktop", 2},
- {"Cougar Point Mobile", 2},
- {"Patsburg", 2},
- {"DH89xxCC", 2},
- {"Panther Point", 2},
- {"Lynx Point", 2},
- {NULL, 0}
-};
-
-/*
- * This data only exists for exporting the supported PCI ids
- * via MODULE_DEVICE_TABLE. We do not actually register a
- * pci_driver, because the I/O Controller Hub has also other
- * functions that probably will be registered by other drivers.
- */
-static DEFINE_PCI_DEVICE_TABLE(iTCO_wdt_pci_tbl) = {
- { PCI_VDEVICE(INTEL, 0x2410), TCO_ICH},
- { PCI_VDEVICE(INTEL, 0x2420), TCO_ICH0},
- { PCI_VDEVICE(INTEL, 0x2440), TCO_ICH2},
- { PCI_VDEVICE(INTEL, 0x244c), TCO_ICH2M},
- { PCI_VDEVICE(INTEL, 0x2480), TCO_ICH3},
- { PCI_VDEVICE(INTEL, 0x248c), TCO_ICH3M},
- { PCI_VDEVICE(INTEL, 0x24c0), TCO_ICH4},
- { PCI_VDEVICE(INTEL, 0x24cc), TCO_ICH4M},
- { PCI_VDEVICE(INTEL, 0x2450), TCO_CICH},
- { PCI_VDEVICE(INTEL, 0x24d0), TCO_ICH5},
- { PCI_VDEVICE(INTEL, 0x25a1), TCO_6300ESB},
- { PCI_VDEVICE(INTEL, 0x2640), TCO_ICH6},
- { PCI_VDEVICE(INTEL, 0x2641), TCO_ICH6M},
- { PCI_VDEVICE(INTEL, 0x2642), TCO_ICH6W},
- { PCI_VDEVICE(INTEL, 0x2670), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2671), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2672), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2673), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2674), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2675), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2676), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2677), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2678), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x2679), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267a), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267b), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267c), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267d), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267e), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x267f), TCO_631XESB},
- { PCI_VDEVICE(INTEL, 0x27b8), TCO_ICH7},
- { PCI_VDEVICE(INTEL, 0x27b0), TCO_ICH7DH},
- { PCI_VDEVICE(INTEL, 0x27b9), TCO_ICH7M},
- { PCI_VDEVICE(INTEL, 0x27bd), TCO_ICH7MDH},
- { PCI_VDEVICE(INTEL, 0x27bc), TCO_NM10},
- { PCI_VDEVICE(INTEL, 0x2810), TCO_ICH8},
- { PCI_VDEVICE(INTEL, 0x2812), TCO_ICH8DH},
- { PCI_VDEVICE(INTEL, 0x2814), TCO_ICH8DO},
- { PCI_VDEVICE(INTEL, 0x2815), TCO_ICH8M},
- { PCI_VDEVICE(INTEL, 0x2811), TCO_ICH8ME},
- { PCI_VDEVICE(INTEL, 0x2918), TCO_ICH9},
- { PCI_VDEVICE(INTEL, 0x2916), TCO_ICH9R},
- { PCI_VDEVICE(INTEL, 0x2912), TCO_ICH9DH},
- { PCI_VDEVICE(INTEL, 0x2914), TCO_ICH9DO},
- { PCI_VDEVICE(INTEL, 0x2919), TCO_ICH9M},
- { PCI_VDEVICE(INTEL, 0x2917), TCO_ICH9ME},
- { PCI_VDEVICE(INTEL, 0x3a18), TCO_ICH10},
- { PCI_VDEVICE(INTEL, 0x3a16), TCO_ICH10R},
- { PCI_VDEVICE(INTEL, 0x3a1a), TCO_ICH10D},
- { PCI_VDEVICE(INTEL, 0x3a14), TCO_ICH10DO},
- { PCI_VDEVICE(INTEL, 0x3b00), TCO_PCH},
- { PCI_VDEVICE(INTEL, 0x3b01), TCO_PCHM},
- { PCI_VDEVICE(INTEL, 0x3b02), TCO_P55},
- { PCI_VDEVICE(INTEL, 0x3b03), TCO_PM55},
- { PCI_VDEVICE(INTEL, 0x3b06), TCO_H55},
- { PCI_VDEVICE(INTEL, 0x3b07), TCO_QM57},
- { PCI_VDEVICE(INTEL, 0x3b08), TCO_H57},
- { PCI_VDEVICE(INTEL, 0x3b09), TCO_HM55},
- { PCI_VDEVICE(INTEL, 0x3b0a), TCO_Q57},
- { PCI_VDEVICE(INTEL, 0x3b0b), TCO_HM57},
- { PCI_VDEVICE(INTEL, 0x3b0d), TCO_PCHMSFF},
- { PCI_VDEVICE(INTEL, 0x3b0f), TCO_QS57},
- { PCI_VDEVICE(INTEL, 0x3b12), TCO_3400},
- { PCI_VDEVICE(INTEL, 0x3b14), TCO_3420},
- { PCI_VDEVICE(INTEL, 0x3b16), TCO_3450},
- { PCI_VDEVICE(INTEL, 0x5031), TCO_EP80579},
- { PCI_VDEVICE(INTEL, 0x1c41), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c42), TCO_CPTD},
- { PCI_VDEVICE(INTEL, 0x1c43), TCO_CPTM},
- { PCI_VDEVICE(INTEL, 0x1c44), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c45), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c46), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c47), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c48), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c49), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4a), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4b), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4c), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4d), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4e), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c4f), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c50), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c51), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c52), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c53), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c54), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c55), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c56), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c57), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c58), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c59), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5a), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5b), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5c), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5d), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5e), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1c5f), TCO_CPT},
- { PCI_VDEVICE(INTEL, 0x1d40), TCO_PBG},
- { PCI_VDEVICE(INTEL, 0x1d41), TCO_PBG},
- { PCI_VDEVICE(INTEL, 0x2310), TCO_DH89XXCC},
- { PCI_VDEVICE(INTEL, 0x1e40), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e41), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e42), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e43), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e44), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e45), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e46), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e47), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e48), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e49), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4a), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4b), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4c), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4d), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4e), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e4f), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e50), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e51), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e52), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e53), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e54), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e55), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e56), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e57), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e58), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e59), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5a), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5b), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5c), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5d), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5e), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x1e5f), TCO_PPT},
- { PCI_VDEVICE(INTEL, 0x8c40), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c41), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c42), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c43), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c44), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c45), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c46), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c47), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c48), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c49), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4a), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4b), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4c), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4d), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4e), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c4f), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c50), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c51), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c52), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c53), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c54), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c55), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c56), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c57), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c58), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c59), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5a), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5b), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5c), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5d), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5e), TCO_LPT},
- { PCI_VDEVICE(INTEL, 0x8c5f), TCO_LPT},
- { 0, }, /* End of list */
-};
-MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl);
-
/* Address definitions for the TCO */
/* TCO base address */
-#define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60)
+#define TCOBASE iTCO_wdt_private.tco_res->start
/* SMI Control and Enable Register */
-#define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30)
+#define SMI_EN iTCO_wdt_private.smi_res->start
#define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */
#define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */
@@ -392,19 +92,18 @@ static char expect_release;
static struct { /* this is private data for the iTCO_wdt device */
/* TCO version/generation */
unsigned int iTCO_version;
- /* The device's ACPIBASE address (TCOBASE = ACPIBASE+0x60) */
- unsigned long ACPIBASE;
+ struct resource *tco_res;
+ struct resource *smi_res;
+ struct resource *gcs_res;
/* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/
unsigned long __iomem *gcs;
/* the lock for io operations */
spinlock_t io_lock;
+ struct platform_device *dev;
/* the PCI-device */
struct pci_dev *pdev;
} iTCO_wdt_private;
-/* the watchdog platform device */
-static struct platform_device *iTCO_wdt_platform_device;
-
/* module parameters */
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
@@ -484,12 +183,12 @@ static int iTCO_wdt_start(void)
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat);
+ iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, heartbeat);
/* disable chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit()) {
spin_unlock(&iTCO_wdt_private.io_lock);
- printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, "
+ pr_err("failed to reset NO_REBOOT flag, "
"reboot disabled by hardware/BIOS\n");
return -EIO;
}
@@ -519,7 +218,7 @@ static int iTCO_wdt_stop(void)
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE);
+ iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res);
/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
val = inw(TCO1_CNT);
@@ -541,7 +240,7 @@ static int iTCO_wdt_keepalive(void)
{
spin_lock(&iTCO_wdt_private.io_lock);
- iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat);
+ iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, heartbeat);
/* Reload the timer by writing to the TCO Timer Counter register */
if (iTCO_wdt_private.iTCO_version == 2)
@@ -661,8 +360,7 @@ static int iTCO_wdt_release(struct inode *inode, struct file *file)
if (expect_release == 42) {
iTCO_wdt_stop();
} else {
- printk(KERN_CRIT PFX
- "Unexpected close, not stopping watchdog!\n");
+ pr_crit("Unexpected close, not stopping watchdog!\n");
iTCO_wdt_keepalive();
}
clear_bit(0, &is_active);
@@ -787,51 +485,71 @@ static struct miscdevice iTCO_wdt_miscdev = {
* Init & exit routines
*/
-static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
- const struct pci_device_id *ent, struct platform_device *dev)
+static void __devexit iTCO_wdt_cleanup(void)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ iTCO_wdt_stop();
+
+ /* Deregister */
+ misc_deregister(&iTCO_wdt_miscdev);
+ release_resource(iTCO_wdt_private.tco_res);
+ release_resource(iTCO_wdt_private.smi_res);
+ release_resource(iTCO_wdt_private.gcs_res);
+ if (iTCO_wdt_private.iTCO_version == 2)
+ iounmap(iTCO_wdt_private.gcs);
+ iTCO_wdt_private.tco_res = NULL;
+ iTCO_wdt_private.smi_res = NULL;
+ iTCO_wdt_private.gcs_res = NULL;
+}
+
+static int __devinit iTCO_wdt_probe(struct platform_device *dev)
{
int ret;
- u32 base_address;
- unsigned long RCBA;
unsigned long val32;
+ struct lpc_ich_info *ich_info = dev->dev.platform_data;
- /*
- * Find the ACPI/PM base I/O address which is the base
- * for the TCO registers (TCOBASE=ACPIBASE + 0x60)
- * ACPIBASE is bits [15:7] from 0x40-0x43
- */
- pci_read_config_dword(pdev, 0x40, &base_address);
- base_address &= 0x0000ff80;
- if (base_address == 0x00000000) {
- /* Something's wrong here, ACPIBASE has to be set */
- printk(KERN_ERR PFX "failed to get TCOBASE address, "
- "device disabled by hardware/BIOS\n");
+ if (!ich_info)
+ return -ENODEV;
+
+ spin_lock_init(&iTCO_wdt_private.io_lock);
+
+ iTCO_wdt_private.tco_res =
+ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO);
+
+ iTCO_wdt_private.smi_res =
+ platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI);
+
+ iTCO_wdt_private.gcs_res =
+ platform_get_resource(dev, IORESOURCE_MEM, ICH_RES_MEM_GCS);
+
+ if (!iTCO_wdt_private.tco_res || !iTCO_wdt_private.smi_res ||
+ !iTCO_wdt_private.gcs_res) {
+ pr_info("No device detected.\n");
return -ENODEV;
}
- iTCO_wdt_private.iTCO_version =
- iTCO_chipset_info[ent->driver_data].iTCO_version;
- iTCO_wdt_private.ACPIBASE = base_address;
- iTCO_wdt_private.pdev = pdev;
-
- /* Get the Memory-Mapped GCS register, we need it for the
- NO_REBOOT flag (TCO v2). To get access to it you have to
- read RCBA from PCI Config space 0xf0 and use it as base.
- GCS = RCBA + ICH6_GCS(0x3410). */
+
+ iTCO_wdt_private.iTCO_version = ich_info->iTCO_version;
+ iTCO_wdt_private.dev = dev;
+ iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent);
+
+ /*
+ * Get the Memory-Mapped GCS register, we need it for the
+ * NO_REBOOT flag (TCO v2).
+ */
if (iTCO_wdt_private.iTCO_version == 2) {
- pci_read_config_dword(pdev, 0xf0, &base_address);
- if ((base_address & 1) == 0) {
- printk(KERN_ERR PFX "RCBA is disabled by hardware"
- "/BIOS, device disabled\n");
- ret = -ENODEV;
+ if (!request_mem_region(iTCO_wdt_private.gcs_res->start,
+ resource_size(iTCO_wdt_private.gcs_res), dev->name)) {
+ ret = -EBUSY;
goto out;
}
- RCBA = base_address & 0xffffc000;
- iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4);
+ iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start,
+ resource_size(iTCO_wdt_private.gcs_res));
}
/* Check chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) {
- printk(KERN_INFO PFX "unable to reset NO_REBOOT flag, "
+ pr_info("unable to reset NO_REBOOT flag, "
"device disabled by hardware/BIOS\n");
ret = -ENODEV; /* Cannot reset NO_REBOOT bit */
goto out_unmap;
@@ -841,11 +559,11 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
iTCO_wdt_set_NO_REBOOT_bit();
/* The TCO logic uses the TCO_EN bit in the SMI_EN register */
- if (!request_region(SMI_EN, 4, "iTCO_wdt")) {
- printk(KERN_ERR PFX
- "I/O address 0x%04lx already in use, "
+ if (!request_region(iTCO_wdt_private.smi_res->start,
+ resource_size(iTCO_wdt_private.smi_res), dev->name)) {
+ pr_err("I/O address 0x%04llx already in use, "
"device disabled\n", SMI_EN);
- ret = -EIO;
+ ret = -EBUSY;
goto out_unmap;
}
if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) {
@@ -855,20 +573,16 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
outl(val32, SMI_EN);
}
- /* The TCO I/O registers reside in a 32-byte range pointed to
- by the TCOBASE value */
- if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) {
- printk(KERN_ERR PFX "I/O address 0x%04lx already in use "
- "device disabled\n", TCOBASE);
- ret = -EIO;
+ if (!request_region(iTCO_wdt_private.tco_res->start,
+ resource_size(iTCO_wdt_private.tco_res), dev->name)) {
+ pr_err("I/O address 0x%04llx already in use device disabled\n",
+ TCOBASE);
+ ret = -EBUSY;
goto unreg_smi_en;
}
- printk(KERN_INFO PFX
- "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n",
- iTCO_chipset_info[ent->driver_data].name,
- iTCO_chipset_info[ent->driver_data].iTCO_version,
- TCOBASE);
+ pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n",
+ ich_info->name, ich_info->iTCO_version, TCOBASE);
/* Clear out the (probably old) status */
outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */
@@ -882,79 +596,38 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
if not reset to the default */
if (iTCO_wdt_set_heartbeat(heartbeat)) {
iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT);
- printk(KERN_INFO PFX
- "timeout value out of range, using %d\n", heartbeat);
+ pr_info("timeout value out of range, using %d\n", heartbeat);
}
ret = misc_register(&iTCO_wdt_miscdev);
if (ret != 0) {
- printk(KERN_ERR PFX
- "cannot register miscdev on minor=%d (err=%d)\n",
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_region;
}
- printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
heartbeat, nowayout);
return 0;
unreg_region:
- release_region(TCOBASE, 0x20);
+ release_resource(iTCO_wdt_private.tco_res);
unreg_smi_en:
- release_region(SMI_EN, 4);
+ release_resource(iTCO_wdt_private.tco_res);
out_unmap:
if (iTCO_wdt_private.iTCO_version == 2)
iounmap(iTCO_wdt_private.gcs);
out:
- iTCO_wdt_private.ACPIBASE = 0;
- return ret;
-}
-
-static void __devexit iTCO_wdt_cleanup(void)
-{
- /* Stop the timer before we leave */
- if (!nowayout)
- iTCO_wdt_stop();
-
- /* Deregister */
- misc_deregister(&iTCO_wdt_miscdev);
- release_region(TCOBASE, 0x20);
- release_region(SMI_EN, 4);
- if (iTCO_wdt_private.iTCO_version == 2)
- iounmap(iTCO_wdt_private.gcs);
- pci_dev_put(iTCO_wdt_private.pdev);
- iTCO_wdt_private.ACPIBASE = 0;
-}
-
-static int __devinit iTCO_wdt_probe(struct platform_device *dev)
-{
- int ret = -ENODEV;
- int found = 0;
- struct pci_dev *pdev = NULL;
- const struct pci_device_id *ent;
-
- spin_lock_init(&iTCO_wdt_private.io_lock);
-
- for_each_pci_dev(pdev) {
- ent = pci_match_id(iTCO_wdt_pci_tbl, pdev);
- if (ent) {
- found++;
- ret = iTCO_wdt_init(pdev, ent, dev);
- if (!ret)
- break;
- }
- }
-
- if (!found)
- printk(KERN_INFO PFX "No device detected.\n");
+ iTCO_wdt_private.tco_res = NULL;
+ iTCO_wdt_private.smi_res = NULL;
return ret;
}
static int __devexit iTCO_wdt_remove(struct platform_device *dev)
{
- if (iTCO_wdt_private.ACPIBASE)
+ if (iTCO_wdt_private.tco_res || iTCO_wdt_private.smi_res)
iTCO_wdt_cleanup();
return 0;
@@ -979,32 +652,19 @@ static int __init iTCO_wdt_init_module(void)
{
int err;
- printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s\n",
- DRV_VERSION);
+ pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION);
err = platform_driver_register(&iTCO_wdt_driver);
if (err)
return err;
- iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME,
- -1, NULL, 0);
- if (IS_ERR(iTCO_wdt_platform_device)) {
- err = PTR_ERR(iTCO_wdt_platform_device);
- goto unreg_platform_driver;
- }
-
return 0;
-
-unreg_platform_driver:
- platform_driver_unregister(&iTCO_wdt_driver);
- return err;
}
static void __exit iTCO_wdt_cleanup_module(void)
{
- platform_device_unregister(iTCO_wdt_platform_device);
platform_driver_unregister(&iTCO_wdt_driver);
- printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
+ pr_info("Watchdog Module Unloaded.\n");
}
module_init(iTCO_wdt_init_module);
diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
index 286c778..47d64e7 100644
--- a/include/linux/mfd/lpc_ich.h
+++ b/include/linux/mfd/lpc_ich.h
@@ -20,12 +20,19 @@
#ifndef LPC_ICH_H
#define LPC_ICH_H
+/* Watchdog resources */
+#define ICH_RES_IO_TCO 0
+#define ICH_RES_IO_SMI 1
+#define ICH_RES_MEM_OFF 2
+#define ICH_RES_MEM_GCS 0
+
/* GPIO resources */
#define ICH_RES_GPIO 0
#define ICH_RES_GPE0 1
struct lpc_ich_info {
char name[32];
+ unsigned int iTCO_version;
unsigned int gpio_version;
};
--
1.7.0.4
Hi Aaron,
On Tue, 2012-02-07 at 14:56 -0500, Aaron Sierra wrote:
> This driver currently creates resources for use by a forthcoming ICH
> chipset GPIO driver. It could be expanded to created the resources for
> converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
> drivers to use the mfd model.
>
> Signed-off-by: Aaron Sierra <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
> ---
Not sure how to handle this since some of the code is from me ....
usually one doesn't add other people's Signed-off unless both are in the
commit chain. Comments, anyone ?
Anyway, couple of comments below.
> drivers/mfd/Kconfig | 9 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/lpc_ich.c | 510 +++++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/lpc_ich.h | 32 +++
> 4 files changed, 552 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mfd/lpc_ich.c
> create mode 100644 include/linux/mfd/lpc_ich.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index cd13e9f..f581e59 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -724,6 +724,15 @@ config LPC_SCH
> LPC bridge function of the Intel SCH provides support for
> System Management Bus and General Purpose I/O.
>
> +config LPC_ICH
> + tristate "Intel ICH LPC"
> + depends on PCI
> + select MFD_CORE
> + help
> + The LPC bridge function of the Intel ICH provides support for
> + many functional units. This driver provides needed support for
> + other drivers to control these functions, currently GPIO.
> +
> config MFD_RDC321X
> tristate "Support for RDC-R321x southbridge"
> select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index b953bab..6636c5a 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -98,6 +98,7 @@ obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o
> obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o
> obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
> obj-$(CONFIG_LPC_SCH) += lpc_sch.o
> +obj-$(CONFIG_LPC_ICH) += lpc_ich.o
> obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
> obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
> obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
> diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
> new file mode 100644
> index 0000000..3f159fc
> --- /dev/null
> +++ b/drivers/mfd/lpc_ich.c
> @@ -0,0 +1,510 @@
> +/*
> + * lpc_ich.c - LPC interface for Intel ICH
> + *
> + * LPC bridge function of the Intel ICH contains many other
> + * functional units, such as Interrupt controllers, Timers,
> + * Power Management, System Management, GPIO, RTC, and LPC
> + * Configuration Registers.
> + *
> + * This driver is derived from lpc_sch.
> +
> + * Copyright (c) 2011 Extreme Engineering Solution, Inc.
> + * Author: Aaron Sierra <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License 2 as published
> + * by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; see the file COPYING. If not, write to
> + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + *
> + * This driver supports the following I/O Controller hubs:
> + * (See the intel documentation on http://developer.intel.com.)
> + * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO)
> + * document number 290687-002, 298242-027: 82801BA (ICH2)
> + * document number 290733-003, 290739-013: 82801CA (ICH3-S)
> + * document number 290716-001, 290718-007: 82801CAM (ICH3-M)
> + * document number 290744-001, 290745-025: 82801DB (ICH4)
> + * document number 252337-001, 252663-008: 82801DBM (ICH4-M)
> + * document number 273599-001, 273645-002: 82801E (C-ICH)
> + * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R)
> + * document number 300641-004, 300884-013: 6300ESB
> + * document number 301473-002, 301474-026: 82801F (ICH6)
> + * document number 313082-001, 313075-006: 631xESB, 632xESB
> + * document number 307013-003, 307014-024: 82801G (ICH7)
> + * document number 322896-001, 322897-001: NM10
> + * document number 313056-003, 313057-017: 82801H (ICH8)
> + * document number 316972-004, 316973-012: 82801I (ICH9)
> + * document number 319973-002, 319974-002: 82801J (ICH10)
> + * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH)
> + * document number 320066-003, 320257-008: EP80597 (IICH)
> + * document number 324645-001, 324646-001: Cougar Point (CPT)
> + * document number TBD : Patsburg (PBG)
> + * document number TBD : DH89xxCC
> + * document number TBD : Panther Point
> + * document number TBD : Lynx Point
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/errno.h>
> +#include <linux/acpi.h>
> +#include <linux/pci.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/lpc_ich.h>
> +
> +#define ACPIBASE 0x40
> +#define ACPIBASE_GPE_OFF 0x20
> +#define ACPIBASE_GPE_END 0x2f
> +#define ACPICTRL 0x44
> +
> +#define GPIOBASE 0x48
> +#define GPIOCTRL 0x4C
> +#define GPIOBASE_IO_SIZE 0x80
> +
> +static int lpc_ich_acpi_save = -1;
> +static int lpc_ich_gpio_save = -1;
> +
> +static struct resource gpio_ich_res[] = {
> + /* BASE */
> + {
> + .flags = IORESOURCE_IO,
> + },
> + /* ACPI */
> + {
> + .flags = IORESOURCE_IO,
> + },
> +};
> +
> +enum lpc_cells {
> + LPC_GPIO = 0,
> +};
> +
> +static struct mfd_cell lpc_ich_cells[] = {
> + [LPC_GPIO] = {
> + .name = "gpio_ich",
> + .num_resources = ARRAY_SIZE(gpio_ich_res),
> + .resources = gpio_ich_res,
> + },
> +};
> +
> +/* chipset related info */
> +enum lpc_chipsets {
> + LPC_ICH = 0, /* ICH */
> + LPC_ICH0, /* ICH0 */
> + LPC_ICH2, /* ICH2 */
> + LPC_ICH2M, /* ICH2-M */
> + LPC_ICH3, /* ICH3-S */
> + LPC_ICH3M, /* ICH3-M */
> + LPC_ICH4, /* ICH4 */
> + LPC_ICH4M, /* ICH4-M */
> + LPC_CICH, /* C-ICH */
> + LPC_ICH5, /* ICH5 & ICH5R */
> + LPC_6300ESB, /* 6300ESB */
> + LPC_ICH6, /* ICH6 & ICH6R */
> + LPC_ICH6M, /* ICH6-M */
> + LPC_ICH6W, /* ICH6W & ICH6RW */
> + LPC_631XESB, /* 631xESB/632xESB */
> + LPC_ICH7, /* ICH7 & ICH7R */
> + LPC_ICH7DH, /* ICH7DH */
> + LPC_ICH7M, /* ICH7-M & ICH7-U */
> + LPC_ICH7MDH, /* ICH7-M DH */
> + LPC_NM10, /* NM10 */
> + LPC_ICH8, /* ICH8 & ICH8R */
> + LPC_ICH8DH, /* ICH8DH */
> + LPC_ICH8DO, /* ICH8DO */
> + LPC_ICH8M, /* ICH8M */
> + LPC_ICH8ME, /* ICH8M-E */
> + LPC_ICH9, /* ICH9 */
> + LPC_ICH9R, /* ICH9R */
> + LPC_ICH9DH, /* ICH9DH */
> + LPC_ICH9DO, /* ICH9DO */
> + LPC_ICH9M, /* ICH9M */
> + LPC_ICH9ME, /* ICH9M-E */
> + LPC_ICH10, /* ICH10 */
> + LPC_ICH10R, /* ICH10R */
> + LPC_ICH10D, /* ICH10D */
> + LPC_ICH10DO, /* ICH10DO */
> + LPC_PCH, /* PCH Desktop Full Featured */
> + LPC_PCHM, /* PCH Mobile Full Featured */
> + LPC_P55, /* P55 */
> + LPC_PM55, /* PM55 */
> + LPC_H55, /* H55 */
> + LPC_QM57, /* QM57 */
> + LPC_H57, /* H57 */
> + LPC_HM55, /* HM55 */
> + LPC_Q57, /* Q57 */
> + LPC_HM57, /* HM57 */
> + LPC_PCHMSFF, /* PCH Mobile SFF Full Featured */
> + LPC_QS57, /* QS57 */
> + LPC_3400, /* 3400 */
> + LPC_3420, /* 3420 */
> + LPC_3450, /* 3450 */
> + LPC_EP80579, /* EP80579 */
> + LPC_CPT, /* Cougar Point */
> + LPC_CPTD, /* Cougar Point Desktop */
> + LPC_CPTM, /* Cougar Point Mobile */
> + LPC_PBG, /* Patsburg */
> + LPC_DH89XXCC, /* DH89xxCC */
> + LPC_PPT, /* Panther Point */
> + LPC_LPT, /* Lynx Point */
> +};
> +
> +struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
> + [LPC_ICH] = {"ICH", 0},
> + [LPC_ICH0] = {"ICH0", 0},
> + [LPC_ICH2] = {"ICH2", 0},
> + [LPC_ICH2M] = {"ICH2-M", 0},
> + [LPC_ICH3] = {"ICH3-S", 0},
> + [LPC_ICH3M] = {"ICH3-M", 0},
> + [LPC_ICH4] = {"ICH4", 0},
> + [LPC_ICH4M] = {"ICH4-M", 0},
> + [LPC_CICH] = {"C-ICH", 0},
> + [LPC_ICH5] = {"ICH5 or ICH5R", 0},
> + [LPC_6300ESB] = {"6300ESB", 0},
> + [LPC_ICH6] = {"ICH6 or ICH6R", 0x0601},
> + [LPC_ICH6M] = {"ICH6-M", 0x0601},
> + [LPC_ICH6W] = {"ICH6W or ICH6RW", 0x0601},
> + [LPC_631XESB] = {"631xESB/632xESB", 0x0601},
> + [LPC_ICH7] = {"ICH7 or ICH7R", 0x0701},
> + [LPC_ICH7DH] = {"ICH7DH", 0x0701},
> + [LPC_ICH7M] = {"ICH7-M or ICH7-U", 0x0701},
> + [LPC_ICH7MDH] = {"ICH7-M DH", 0x0701},
> + [LPC_NM10] = {"NM10", 0},
> + [LPC_ICH8] = {"ICH8 or ICH8R", 0x0701},
> + [LPC_ICH8DH] = {"ICH8DH", 0x0701},
> + [LPC_ICH8DO] = {"ICH8DO", 0x0701},
> + [LPC_ICH8M] = {"ICH8M", 0x0701},
> + [LPC_ICH8ME] = {"ICH8M-E", 0x0701},
> + [LPC_ICH9] = {"ICH9", 0x0801},
> + [LPC_ICH9R] = {"ICH9R", 0x0801},
> + [LPC_ICH9DH] = {"ICH9DH", 0x0801},
> + [LPC_ICH9DO] = {"ICH9DO", 0x0801},
> + [LPC_ICH9M] = {"ICH9M", 0x0801},
> + [LPC_ICH9ME] = {"ICH9M-E", 0x0801},
> + [LPC_ICH10] = {"ICH10", 0x0a11},
> + [LPC_ICH10R] = {"ICH10R", 0x0a11},
> + [LPC_ICH10D] = {"ICH10D", 0x0a01},
> + [LPC_ICH10DO] = {"ICH10DO", 0x0a01},
> + [LPC_PCH] = {"PCH Desktop Full Featured", 0x0501},
> + [LPC_PCHM] = {"PCH Mobile Full Featured", 0x0501},
> + [LPC_P55] = {"P55", 0x0501},
> + [LPC_PM55] = {"PM55", 0x0501},
> + [LPC_H55] = {"H55", 0x0501},
> + [LPC_QM57] = {"QM57", 0x0501},
> + [LPC_H57] = {"H57", 0x0501},
> + [LPC_HM55] = {"HM55", 0x0501},
> + [LPC_Q57] = {"Q57", 0x0501},
> + [LPC_HM57] = {"HM57", 0x0501},
> + [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",0x0501},
> + [LPC_QS57] = {"QS57", 0x0501},
> + [LPC_3400] = {"3400", 0x0501},
> + [LPC_3420] = {"3420", 0x0501},
> + [LPC_3450] = {"3450", 0x0501},
> + [LPC_EP80579] = {"EP80579", 0},
> + [LPC_CPT] = {"Cougar Point", 0x0501},
> + [LPC_CPTD] = {"Cougar Point Desktop", 0x0501},
> + [LPC_CPTM] = {"Cougar Point Mobile", 0x0501},
> + [LPC_PBG] = {"Patsburg", 0},
> + [LPC_DH89XXCC] = {"DH89xxCC", 0},
> + [LPC_PPT] = {"Panther Point", 0},
> + [LPC_LPT] = {"Lynx Point", 0},
> +};
> +
> +/*
> + * This data only exists for exporting the supported PCI ids
> + * via MODULE_DEVICE_TABLE. We do not actually register a
> + * pci_driver, because the I/O Controller Hub has also other
> + * functions that probably will be registered by other drivers.
> + */
> +static DEFINE_PCI_DEVICE_TABLE(lpc_ich_ids) = {
> + { PCI_VDEVICE(INTEL, 0x2410), LPC_ICH},
> + { PCI_VDEVICE(INTEL, 0x2420), LPC_ICH0},
> + { PCI_VDEVICE(INTEL, 0x2440), LPC_ICH2},
> + { PCI_VDEVICE(INTEL, 0x244c), LPC_ICH2M},
> + { PCI_VDEVICE(INTEL, 0x2480), LPC_ICH3},
> + { PCI_VDEVICE(INTEL, 0x248c), LPC_ICH3M},
> + { PCI_VDEVICE(INTEL, 0x24c0), LPC_ICH4},
> + { PCI_VDEVICE(INTEL, 0x24cc), LPC_ICH4M},
> + { PCI_VDEVICE(INTEL, 0x2450), LPC_CICH},
> + { PCI_VDEVICE(INTEL, 0x24d0), LPC_ICH5},
> + { PCI_VDEVICE(INTEL, 0x25a1), LPC_6300ESB},
> + { PCI_VDEVICE(INTEL, 0x2640), LPC_ICH6},
> + { PCI_VDEVICE(INTEL, 0x2641), LPC_ICH6M},
> + { PCI_VDEVICE(INTEL, 0x2642), LPC_ICH6W},
> + { PCI_VDEVICE(INTEL, 0x2670), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2671), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2672), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2673), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2674), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2675), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2676), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2677), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2678), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x2679), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267a), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267b), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267c), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267d), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267e), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x267f), LPC_631XESB},
> + { PCI_VDEVICE(INTEL, 0x27b8), LPC_ICH7},
> + { PCI_VDEVICE(INTEL, 0x27b0), LPC_ICH7DH},
> + { PCI_VDEVICE(INTEL, 0x27b9), LPC_ICH7M},
> + { PCI_VDEVICE(INTEL, 0x27bd), LPC_ICH7MDH},
> + { PCI_VDEVICE(INTEL, 0x27bc), LPC_NM10},
> + { PCI_VDEVICE(INTEL, 0x2810), LPC_ICH8},
> + { PCI_VDEVICE(INTEL, 0x2812), LPC_ICH8DH},
> + { PCI_VDEVICE(INTEL, 0x2814), LPC_ICH8DO},
> + { PCI_VDEVICE(INTEL, 0x2815), LPC_ICH8M},
> + { PCI_VDEVICE(INTEL, 0x2811), LPC_ICH8ME},
> + { PCI_VDEVICE(INTEL, 0x2918), LPC_ICH9},
> + { PCI_VDEVICE(INTEL, 0x2916), LPC_ICH9R},
> + { PCI_VDEVICE(INTEL, 0x2912), LPC_ICH9DH},
> + { PCI_VDEVICE(INTEL, 0x2914), LPC_ICH9DO},
> + { PCI_VDEVICE(INTEL, 0x2919), LPC_ICH9M},
> + { PCI_VDEVICE(INTEL, 0x2917), LPC_ICH9ME},
> + { PCI_VDEVICE(INTEL, 0x3a18), LPC_ICH10},
> + { PCI_VDEVICE(INTEL, 0x3a16), LPC_ICH10R},
> + { PCI_VDEVICE(INTEL, 0x3a1a), LPC_ICH10D},
> + { PCI_VDEVICE(INTEL, 0x3a14), LPC_ICH10DO},
> + { PCI_VDEVICE(INTEL, 0x3b00), LPC_PCH},
> + { PCI_VDEVICE(INTEL, 0x3b01), LPC_PCHM},
> + { PCI_VDEVICE(INTEL, 0x3b02), LPC_P55},
> + { PCI_VDEVICE(INTEL, 0x3b03), LPC_PM55},
> + { PCI_VDEVICE(INTEL, 0x3b06), LPC_H55},
> + { PCI_VDEVICE(INTEL, 0x3b07), LPC_QM57},
> + { PCI_VDEVICE(INTEL, 0x3b08), LPC_H57},
> + { PCI_VDEVICE(INTEL, 0x3b09), LPC_HM55},
> + { PCI_VDEVICE(INTEL, 0x3b0a), LPC_Q57},
> + { PCI_VDEVICE(INTEL, 0x3b0b), LPC_HM57},
> + { PCI_VDEVICE(INTEL, 0x3b0d), LPC_PCHMSFF},
> + { PCI_VDEVICE(INTEL, 0x3b0f), LPC_QS57},
> + { PCI_VDEVICE(INTEL, 0x3b12), LPC_3400},
> + { PCI_VDEVICE(INTEL, 0x3b14), LPC_3420},
> + { PCI_VDEVICE(INTEL, 0x3b16), LPC_3450},
> + { PCI_VDEVICE(INTEL, 0x5031), LPC_EP80579},
> + { PCI_VDEVICE(INTEL, 0x1c41), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c42), LPC_CPTD},
> + { PCI_VDEVICE(INTEL, 0x1c43), LPC_CPTM},
> + { PCI_VDEVICE(INTEL, 0x1c44), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c45), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c46), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c47), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c48), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c49), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4a), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4b), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4c), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4d), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4e), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c4f), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c50), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c51), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c52), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c53), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c54), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c55), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c56), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c57), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c58), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c59), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5a), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5b), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5c), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5d), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5e), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1c5f), LPC_CPT},
> + { PCI_VDEVICE(INTEL, 0x1d40), LPC_PBG},
> + { PCI_VDEVICE(INTEL, 0x1d41), LPC_PBG},
> + { PCI_VDEVICE(INTEL, 0x2310), LPC_DH89XXCC},
> + { PCI_VDEVICE(INTEL, 0x1e40), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e41), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e42), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e43), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e44), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e45), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e46), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e47), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e48), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e49), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4a), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4b), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4c), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4d), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4e), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e4f), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e50), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e51), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e52), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e53), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e54), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e55), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e56), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e57), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e58), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e59), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5a), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5b), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5c), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5d), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5e), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x1e5f), LPC_PPT},
> + { PCI_VDEVICE(INTEL, 0x8c40), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c41), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c42), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c43), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c44), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c45), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c46), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c47), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c48), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c49), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4a), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4b), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4c), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4d), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4e), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c4f), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c50), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c51), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c52), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c53), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c54), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c55), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c56), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c57), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c58), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c59), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5a), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5b), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5c), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5d), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5e), LPC_LPT},
> + { PCI_VDEVICE(INTEL, 0x8c5f), LPC_LPT},
> + { 0, }, /* End of list */
> +};
> +MODULE_DEVICE_TABLE(pci, lpc_ich_ids);
> +
> +static void lpc_ich_finalize_cell(struct mfd_cell *cell,
> + const struct pci_device_id *id)
> +{
> + cell->id = id->driver_data;
> + cell->platform_data = &lpc_chipset_info[id->driver_data];
> + cell->pdata_size = sizeof(struct lpc_ich_info);
> +}
> +
> +static int __devinit lpc_ich_probe(struct pci_dev *dev,
> + const struct pci_device_id *id)
> +{
> + u32 base_addr_cfg;
> + u32 base_addr;
> + u8 reg_save;
> + int ret;
> + int cells = 0;
> + int acpi_conflict = 0;
> +
You can use bool for acpi_conflict (and cells, but I don't think that is
needed anyway).
> + /* Setup power management base register */
> + pci_read_config_dword(dev, ACPIBASE, &base_addr_cfg);
> + base_addr = base_addr_cfg & 0x0000ff80;
> + if (!base_addr) {
> + dev_err(&dev->dev, "I/O space for ACPI uninitialized\n");
> + goto pm_done;
> + }
> +
> + gpio_ich_res[ICH_RES_GPE0].start = base_addr + ACPIBASE_GPE_OFF;
> + gpio_ich_res[ICH_RES_GPE0].end = base_addr + ACPIBASE_GPE_END;
> + ret = acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPE0]);
> + if (ret) {
> + /* this isn't necessarily fatal for the GPIO */
> + gpio_ich_res[ICH_RES_GPE0].start = 0;
> + gpio_ich_res[ICH_RES_GPE0].end = 0;
> + acpi_conflict++;
acpi_conflict = true;
> + }
> +
> + /* Enable LPC ACPI space */
> + pci_read_config_byte(dev, ACPICTRL, ®_save);
> + pci_write_config_byte(dev, ACPICTRL, reg_save | 0x10);
> + lpc_ich_acpi_save = (int)reg_save;
> +
Unnecessary typecast.
> +pm_done:
> + /* Setup GPIO base register */
> + pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
> + base_addr = base_addr_cfg & 0x0000ff80;
> + if (!base_addr) {
> + dev_err(&dev->dev, "I/O space for GPIO uninitialized\n");
> + /* GPIO in power-management space may still be available */
> + goto gpio_reg;
> + }
> +
> + gpio_ich_res[ICH_RES_GPIO].start = base_addr;
> + gpio_ich_res[ICH_RES_GPIO].end = base_addr + GPIOBASE_IO_SIZE - 1;
> + ret = acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPIO]);
> + if (ret) {
> + /* this isn't necessarily fatal for the GPIO */
> + gpio_ich_res[ICH_RES_GPIO].start = 0;
> + gpio_ich_res[ICH_RES_GPIO].end = 0;
> + acpi_conflict++;
acpi_conflict = true;
After all, it does not really matter how many conflicts were detected.
> + goto gpio_reg;
> + }
> +
> + /* Enable LPC GPIO space */
> + pci_read_config_byte(dev, GPIOCTRL, ®_save);
> + pci_write_config_byte(dev, GPIOCTRL, reg_save | 0x10);
> + lpc_ich_gpio_save = (int)reg_save;
> +
That typecast is unnecessary.
> +gpio_reg:
> + lpc_ich_finalize_cell(&lpc_ich_cells[LPC_GPIO], id);
> + ret = mfd_add_devices(&dev->dev, 0, &lpc_ich_cells[LPC_GPIO],
> + 1, NULL, 0);
> + if (!ret)
> + cells++;
> +
So if there is an error, ret < 0, correct ?
> + if (acpi_conflict)
> + dev_info(&dev->dev, "ACPI resource conflicts found; "
> + "consider using acpi_enforce_resources=lax?\n");
> +
> + if (cells)
> + return 0;
> + else
> + return -ENODEV;
If the above is true, you can just return ret, and you don't need the
cells variable. Or, even better, move the acpi warning above the call to
mfd_add_devices().
> +}
> +
> +static void __devexit lpc_ich_remove(struct pci_dev *dev)
> +{
> + mfd_remove_devices(&dev->dev);
> +
> + pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save);
> + pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save);
> +}
> +
> +static struct pci_driver lpc_ich_driver = {
> + .name = "lpc_ich",
> + .id_table = lpc_ich_ids,
> + .probe = lpc_ich_probe,
> + .remove = __devexit_p(lpc_ich_remove),
> +};
> +
> +static int __init lpc_ich_init(void)
> +{
> + return pci_register_driver(&lpc_ich_driver);
> +}
> +
> +static void __exit lpc_ich_exit(void)
> +{
> + pci_unregister_driver(&lpc_ich_driver);
> +}
> +
> +module_init(lpc_ich_init);
> +module_exit(lpc_ich_exit);
> +
> +MODULE_AUTHOR("Aaron Sierra <[email protected]>");
> +MODULE_DESCRIPTION("LPC interface for Intel ICH");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
> new file mode 100644
> index 0000000..286c778
> --- /dev/null
> +++ b/include/linux/mfd/lpc_ich.h
> @@ -0,0 +1,32 @@
> +/*
> + * linux/drivers/mfd/lpc_ich.h
> + *
> + * Copyright (c) 2012 Extreme Engineering Solution, Inc.
> + * Author: Aaron Sierra <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License 2 as published
> + * by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; see the file COPYING. If not, write to
> + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +#ifndef LPC_ICH_H
> +#define LPC_ICH_H
> +
> +/* GPIO resources */
> +#define ICH_RES_GPIO 0
> +#define ICH_RES_GPE0 1
> +
> +struct lpc_ich_info {
> + char name[32];
> + unsigned int gpio_version;
> +};
> +
> +#endif
> --
> 1.7.0.4
On Tue, 7 Feb 2012 12:15:17 -0800, Guenter Roeck wrote:
> Hi Aaron,
>
> On Tue, 2012-02-07 at 14:56 -0500, Aaron Sierra wrote:
> > This driver currently creates resources for use by a forthcoming ICH
> > chipset GPIO driver. It could be expanded to created the resources for
> > converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
> > drivers to use the mfd model.
> >
> > Signed-off-by: Aaron Sierra <[email protected]>
> > Signed-off-by: Guenter Roeck <[email protected]>
> > ---
>
> Not sure how to handle this since some of the code is from me ....
> usually one doesn't add other people's Signed-off unless both are in the
> commit chain. Comments, anyone ?
If the code is co-written and you both agree with what the code
contributed by the other (in terms of Developer's Certificate of
Origin) then it's IMHO perfectly fine to have two Signed-off-by in the
submission.
At any rate it is much better to do that that to commit a known-broken
patch and have the second contributor fix it afterward.
--
Jean Delvare
On Tue, 2012-02-07 at 14:58 -0500, Aaron Sierra wrote:
> This driver works on many Intel chipsets, including the ICH6, ICH7,
> ICH8, ICH9, ICH10, 3100, Series 5/3400 (Ibex Peak), Series 6/C200
> (Cougar Point), and NM10 (Tiger Point).
>
> Additional Intel chipsets should be easily supported if needed, eg the
> ICH1-5, EP80579, etc.
>
> Tested on QM67 (Cougar Point), QM57 (Ibex Peak), 3100 (Whitmore Lake),
> and NM10 (Tiger Point).
>
> Signed-off-by: Aaron Sierra <[email protected]>
> Signed-off-by: Peter Tyser <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
Hi Aaron,
that Sign-off is surely inappropriate here. I provided a couple of
review comments, but nothing really substantial to the code. You might
want to mention, though, that Jean provided some input and cleanup.
Couple of comments below.
> ---
> MAINTAINERS | 6 +
> drivers/gpio/Kconfig | 13 ++
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-ich.c | 437 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 457 insertions(+), 0 deletions(-)
> create mode 100644 drivers/gpio/gpio-ich.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a1fce9a..9a0b59f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3284,6 +3284,12 @@ W: http://www.developer.ibm.com/welcome/netfinity/serveraid.html
> S: Supported
> F: drivers/scsi/ips.*
>
> +ICH LPC DRIVER
> +M: Peter Tyser <[email protected]>
> +S: Maintained
> +F: drivers/mfd/lpc_ich.c
> +F: drivers/gpio/gpio-ich.c
> +
> IDE SUBSYSTEM
> M: "David S. Miller" <[email protected]>
> L: [email protected]
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index d0c4118..3359f1e 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -178,6 +178,19 @@ config GPIO_SCH
> The Intel Tunnel Creek processor has 5 GPIOs powered by the
> core power rail and 9 from suspend power supply.
>
> +config GPIO_ICH
> + tristate "Intel ICH GPIO"
> + depends on PCI && X86
> + select MFD_CORE
> + select LPC_ICH
> + help
> + Say yes here to support the GPIO functionality of a number of Intel
> + ICH-based chipsets. Currently supported devices: ICH6, ICH7, ICH8
> + ICH9, ICH10, Series 5/3400 (eg Ibex Peak), Series 6/C200 (eg
> + Cougar Point), NM10 (Tiger Point), and 3100 (Whitmore Lake).
> +
> + If unsure, say N.
> +
> config GPIO_VX855
> tristate "VIA VX855/VX875 GPIO"
> depends on PCI
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index fa10df6..b538830 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -44,6 +44,7 @@ obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
> obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
> obj-$(CONFIG_PLAT_SAMSUNG) += gpio-samsung.o
> obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o
> +obj-$(CONFIG_GPIO_ICH) += gpio-ich.o
> obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
> obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o
> obj-$(CONFIG_GPIO_SX150X) += gpio-sx150x.o
> diff --git a/drivers/gpio/gpio-ich.c b/drivers/gpio/gpio-ich.c
> new file mode 100644
> index 0000000..0bf76dc
> --- /dev/null
> +++ b/drivers/gpio/gpio-ich.c
> @@ -0,0 +1,437 @@
> +/*
> + * Intel ICH6-10, Series 5 and 6 GPIO driver
> + *
> + * Copyright (C) 2010 Extreme Engineering Solutions.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/lpc_ich.h>
> +
> +#define DRV_NAME "gpio_ich"
> +
> +/* PCI config register offsets into LPC I/F - D31:F0 */
> +#define PCI_ICHX_ACPI_BAR 0x40
> +#define PCI_ICHX_GPIO_BAR 0x48
> +#define PCI_ICHX_GPIO_CTRL 0x4C
> +
> +/*
> + * GPIO register offsets in GPIO I/O space.
> + * Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and
> + * LVLx registers. Logic in the read/write functions takes a register and
> + * an absolute bit number and determines the proper register offset and bit
> + * number in that register. For example, to read the value of GPIO bit 50
> + * the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)],
> + * bit 18 (50%32).
> + */
> +enum GPIO_REG {
> + GPIO_USE_SEL = 0,
> + GPIO_IO_SEL,
> + GPIO_LVL,
> +};
> +
> +static const u8 ichx_regs[3][3] = {
> + {0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */
> + {0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */
> + {0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */
> +};
> +
> +#define ICHX_WRITE(val, reg, base_res) outl(val, (reg) + (base_res)->start)
> +#define ICHX_READ(reg, base_res) inl((reg) + (base_res)->start)
> +
> +struct ichx_desc {
> + /* Max GPIO pins the chipset can have */
> + uint ngpio;
> +
> + /* The offset of GPE0_STS in the PM IO region, 0 if unneeded */
> + uint gpe0_sts_ofs;
> +
> + /* USE_SEL is bogus on some chipsets, eg 3100 */
> + u32 use_sel_ignore[3];
> +
> + /* Some chipsets have quirks, let these use their own request/get */
> + int (*request)(struct gpio_chip *chip, unsigned offset);
> + int (*get)(struct gpio_chip *chip, unsigned offset);
> +};
> +
> +static struct {
> + spinlock_t lock;
> + struct platform_device *dev;
> + struct pci_dev *pdev;
> + struct gpio_chip chip;
> + struct resource *gpio_base; /* GPIO IO base */
> + struct resource *pm_base; /* Power Mangagment IO base */
> + struct ichx_desc *desc; /* Pointer to chipset-specific description */
> + u32 orig_gpio_ctrl; /* Orig CTRL value, used to restore on exit */
> +} ichx_priv;
> +
> +static int modparam_gpiobase = -1; /* dynamic */
> +module_param_named(gpiobase, modparam_gpiobase, int, 0444);
> +MODULE_PARM_DESC(gpiobase, "The GPIO number base. -1 means dynamic, "
> + "which is the default.");
> +
> +static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
> +{
> + unsigned long flags;
> + u32 data, tmp;
> + int reg_nr = nr / 32;
> + int bit = nr & 0x1f;
> + int ret = 0;
> +
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + data = (data & ~(1 << bit)) | (val << bit);
> + ICHX_WRITE(data, ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + tmp = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + if (verify && (data != tmp))
I really dislike those extra ( ).
> + ret = -EPERM;
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return ret;
> +}
> +
> +static int ichx_read_bit(int reg, unsigned nr)
> +{
> + unsigned long flags;
> + u32 data;
> + int reg_nr = nr / 32;
> + int bit = nr & 0x1f;
> +
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return data & (1 << bit) ? 1 : 0;
> +}
> +
> +static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
> +{
> + /*
> + * Try setting pin as an input and verify it worked since many pins
> + * are output-only.
> + */
> + if (ichx_write_bit(GPIO_IO_SEL, nr, 1, 1))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
> + int val)
> +{
> + /* Set GPIO output value. */
> + ichx_write_bit(GPIO_LVL, nr, val, 0);
> +
> + /*
> + * Try setting pin as an output and verify it worked since many pins
> + * are input-only.
> + */
> + if (ichx_write_bit(GPIO_IO_SEL, nr, 0, 1))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr)
> +{
> + return ichx_read_bit(GPIO_LVL, nr);
> +}
> +
> +static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr)
> +{
> + unsigned long flags;
> + u32 data;
> +
> + /*
> + * GPI 0 - 15 need to be read from the power management registers on
> + * a ICH6/3100 bridge.
> + */
> + if (nr < 16 && ichx_priv.pm_base) {
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + /* GPI 0 - 15 are latched, write 1 to clear*/
clear */
> + ICHX_WRITE(1 << (16 + nr), ichx_priv.desc->gpe0_sts_ofs,
> + ichx_priv.pm_base);
> + data = ICHX_READ(ichx_priv.desc->gpe0_sts_ofs,
> + ichx_priv.pm_base);
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return (data >> 16) & (1 << nr) ? 1 : 0;
> + } else {
Should probably return an error if nr < 16 and pm_base is NULL.
> + return ichx_gpio_get(chip, nr);
> + }
> +}
> +
> +static int ichx_gpio_request(struct gpio_chip *chip, unsigned nr)
> +{
> + /*
> + * Note we assume the BIOS properly set a bridge's USE value. Some
> + * chips (eg Intel 3100) have bogus USE values though, so first see if
> + * the chipset's USE value can be trusted for this specific bit.
> + * If it can't be trusted, assume that the pin can be used as a GPIO.
> + */
> + if (ichx_priv.desc->use_sel_ignore[nr / 32] & (1 << (nr & 0x1f)))
> + return 1;
> +
> + return ichx_read_bit(GPIO_USE_SEL, nr) ? 0 : -ENODEV;
> +}
> +
> +static int ich6_gpio_request(struct gpio_chip *chip, unsigned nr)
> +{
> + /*
> + * Fixups for bits 16 and 17 are necessary on the Intel ICH6/3100
> + * bridge as they are controlled by USE register bits 0 and 1. See
> + * "Table 704 GPIO_USE_SEL1 register" in the i3100 datasheet for
> + * additional info.
> + */
> + if ((nr == 16) || (nr == 17))
Same here.
> + nr -= 16;
> +
> + return ichx_gpio_request(chip, nr);
> +}
> +
> +static void ichx_gpio_set(struct gpio_chip *chip, unsigned nr, int val)
> +{
> + ichx_write_bit(GPIO_LVL, nr, val, 0);
> +}
> +
> +static void __devinit ichx_gpiolib_setup(struct gpio_chip *chip)
> +{
> + chip->owner = THIS_MODULE;
> + chip->label = DRV_NAME;
> +
> + /* Allow chip-specific overrides of request()/get() */
> + chip->request = ichx_priv.desc->request ?
> + ichx_priv.desc->request : ichx_gpio_request;
> + chip->get = ichx_priv.desc->get ?
> + ichx_priv.desc->get : ichx_gpio_get;
> +
> + chip->set = ichx_gpio_set;
> + chip->direction_input = ichx_gpio_direction_input;
> + chip->direction_output = ichx_gpio_direction_output;
> + chip->base = modparam_gpiobase;
> + chip->ngpio = ichx_priv.desc->ngpio;
> + chip->can_sleep = 0;
> + chip->dbg_show = NULL;
> +}
> +
> +/* ICH6-based, 631xesb-based */
> +static struct ichx_desc ich6_desc = {
> + /* Bridges using the ICH6 controller need fixups for GPIO 0 - 17 */
> + .request = ich6_gpio_request,
> + .get = ich6_gpio_get,
> +
> + /* GPIO 0-15 are read in the GPE0_STS PM register */
> + .gpe0_sts_ofs = 0x28,
> +
> + .ngpio = 50,
> +};
> +
> +/* Intel 3100 */
> +static struct ichx_desc i3100_desc = {
> + /*
> + * Bits 16,17, 20 of USE_SEL and bit 16 of USE_SEL2 always read 0 on
> + * the Intel 3100. See "Table 712. GPIO Summary Table" of 3100
> + * Datasheet for more info.
> + */
> + .use_sel_ignore = {0x00130000, 0x00010000, 0x0},
> +
> + /* The 3100 needs fixups for GPIO 0 - 17 */
> + .request = ich6_gpio_request,
> + .get = ich6_gpio_get,
> +
> + /* GPIO 0-15 are read in the GPE0_STS PM register */
> + .gpe0_sts_ofs = 0x28,
> +
> + .ngpio = 50,
> +};
> +
> +/* ICH7 and ICH8-based */
> +static struct ichx_desc ich7_desc = {
> + .ngpio = 50,
> +};
> +
> +/* ICH9-based */
> +static struct ichx_desc ich9_desc = {
> + .ngpio = 61,
> +};
> +
> +/* ICH10-based - Consumer/corporate versions have different amount of GPIO */
> +static struct ichx_desc ich10_cons_desc = {
> + .ngpio = 61,
> +};
> +static struct ichx_desc ich10_corp_desc = {
> + .ngpio = 72,
> +};
> +
> +/* Intel 5 series, 6 series, 3400 series, and C200 series */
> +static struct ichx_desc intel5_desc = {
> + .ngpio = 76,
> +};
> +
> +static int __devinit ichx_gpio_probe(struct platform_device *pdev)
> +{
> + struct resource *res_base, *res_pm;
> + int err;
> + struct lpc_ich_info *ich_info = pdev->dev.platform_data;
> +
> + if (!ich_info)
> + return -ENODEV;
> +
> + switch (ich_info-> gpio_version) {
> + case 0x401:
> + ichx_priv.desc = &i3100_desc;
> + break;
> + case 0x501:
> + ichx_priv.desc = &intel5_desc;
> + break;
> + case 0x601:
> + ichx_priv.desc = &ich6_desc;
> + break;
> + case 0x701:
> + ichx_priv.desc = &ich7_desc;
> + break;
> + case 0x801:
> + ichx_priv.desc = &ich9_desc;
> + break;
> + case 0xa01:
> + ichx_priv.desc = &ich10_corp_desc;
> + break;
> + case 0xa11:
> + ichx_priv.desc = &ich10_cons_desc;
> + break;
> + default:
> + return -ENODEV;
> + }
> +
> + res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
> + if (!res_base || !res_base->start || !res_base->end)
> + return -ENODEV;
> +
> + /*
> + * The GPIO resource size reported by lpc_ich is the largest size
> + * for any of the supported chipsets. Older devices provide fewer
> + * GPIO and have a smaller resource size.
> + */
> + if (ichx_priv.desc->ngpio > 64)
> + res_base->end = res_base->start + 64 - 1;
> +
Are you sure this is correct ? Should it be "ngpio < 64" ?
> + if (!request_region(res_base->start, resource_size(res_base),
> + pdev->name))
> + return -EBUSY;
> +
> + ichx_priv.gpio_base = res_base;
> +
> + /*
> + * If necessary, determine the I/O address of ACPI/power management
> + * registers which are needed to read the the GPE0 register for GPI pins
> + * 0 - 15 on some chipsets.
> + */
> + if (!ichx_priv.desc->gpe0_sts_ofs)
> + goto init;
> +
> + res_pm = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPE0);
> + if (!res_pm || !res_pm->start || !res_pm->end) {
> + pr_warn("ACPI BAR is unavailable, GPI 0 - 15 unavailable\n");
> + goto init;
> + }
> +
> + if (!request_region(res_pm->start, resource_size(res_pm),
> + pdev->name)) {
> + pr_warn("ACPI BAR is busy, GPI 0 - 15 unavailable\n");
> + goto init;
> + }
> +
> + ichx_priv.pm_base = res_pm;
> +
> +init:
> + ichx_gpiolib_setup(&ichx_priv.chip);
> + err = gpiochip_add(&ichx_priv.chip);
> + if (err) {
> + pr_err("Failed to register GPIOs\n");
> + goto add_err;
> + }
> +
> + pr_info("GPIO from %d to %d on %s\n", ichx_priv.chip.base,
> + ichx_priv.chip.base + ichx_priv.chip.ngpio - 1, DRV_NAME);
> +
> + return 0;
> +
> +add_err:
> + release_region(ichx_priv.gpio_base->start,
> + resource_size(ichx_priv.gpio_base));
> + if (ichx_priv.pm_base)
> + release_region(ichx_priv.pm_base->start,
> + resource_size(ichx_priv.pm_base));
> + return err;
> +}
> +
> +static int __devexit ichx_gpio_remove(struct platform_device *pdev)
> +{
> + int err;
> +
> + err = gpiochip_remove(&ichx_priv.chip);
> + if (err) {
> + dev_err(&pdev->dev, "%s failed, %d\n",
> + "gpiochip_remove()", err);
> + return err;
> + }
> +
> + release_region(ichx_priv.gpio_base->start,
> + resource_size(ichx_priv.gpio_base));
> + if (ichx_priv.pm_base)
> + release_region(ichx_priv.pm_base->start,
> + resource_size(ichx_priv.pm_base));
> +
> + return 0;
> +}
> +
> +static struct platform_driver ichx_gpio_driver = {
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = DRV_NAME,
> + },
> + .probe = ichx_gpio_probe,
> + .remove = __devexit_p(ichx_gpio_remove),
> +};
> +
> +static int __devinit ichx_gpio_init_module(void)
> +{
> + return platform_driver_register(&ichx_gpio_driver);
> +}
> +
> +static void __devexit ichx_gpio_exit_module(void)
> +{
> + platform_driver_unregister(&ichx_gpio_driver);
> +}
> +
> +subsys_initcall(ichx_gpio_init_module);
> +module_exit(ichx_gpio_exit_module);
> +
> +MODULE_AUTHOR("Peter Tyser <[email protected]>");
> +MODULE_DESCRIPTION("GPIO interface for Intel ICH series");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:"DRV_NAME);
> --
> 1.7.0.4
On Tue, 2012-02-07 at 15:31 -0500, Jean Delvare wrote:
> On Tue, 7 Feb 2012 12:15:17 -0800, Guenter Roeck wrote:
> > Hi Aaron,
> >
> > On Tue, 2012-02-07 at 14:56 -0500, Aaron Sierra wrote:
> > > This driver currently creates resources for use by a forthcoming ICH
> > > chipset GPIO driver. It could be expanded to created the resources for
> > > converting the esb2rom (mtd) and iTCO_wdt (wdt), and potentially more,
> > > drivers to use the mfd model.
> > >
> > > Signed-off-by: Aaron Sierra <[email protected]>
> > > Signed-off-by: Guenter Roeck <[email protected]>
> > > ---
> >
> > Not sure how to handle this since some of the code is from me ....
> > usually one doesn't add other people's Signed-off unless both are in the
> > commit chain. Comments, anyone ?
>
> If the code is co-written and you both agree with what the code
> contributed by the other (in terms of Developer's Certificate of
> Origin) then it's IMHO perfectly fine to have two Signed-off-by in the
> submission.
>
> At any rate it is much better to do that that to commit a known-broken
> patch and have the second contributor fix it afterward.
>
Ok, makes sense.
Thanks,
Guenter
> > +static int __devinit lpc_ich_probe(struct pci_dev *dev,
> > + const struct pci_device_id *id)
> > +{
> > + u32 base_addr_cfg;
> > + u32 base_addr;
> > + u8 reg_save;
> > + int ret;
> > + int cells = 0;
> > + int acpi_conflict = 0;
> > +
> You can use bool for acpi_conflict (and cells, but I don't think that
> is needed anyway).
I agree that bool is a better type choice. See my comment at the end
regarding my reason for using cells.
> > +pm_done:
> > + /* Setup GPIO base register */
> > + pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
> > + base_addr = base_addr_cfg & 0x0000ff80;
> > + if (!base_addr) {
> > + dev_err(&dev->dev, "I/O space for GPIO
> > uninitialized\n");
> > + /* GPIO in power-management space may still be
> > available */
> > + goto gpio_reg;
> > + }
> > +
> > + gpio_ich_res[ICH_RES_GPIO].start = base_addr;
> > + gpio_ich_res[ICH_RES_GPIO].end = base_addr +
> > GPIOBASE_IO_SIZE - 1;
> > + ret =
> > acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPIO]);
> > + if (ret) {
> > + /* this isn't necessarily fatal for the GPIO */
> > + gpio_ich_res[ICH_RES_GPIO].start = 0;
> > + gpio_ich_res[ICH_RES_GPIO].end = 0;
> > + acpi_conflict++;
>
> acpi_conflict = true;
>
> After all, it does not really matter how many conflicts were
> detected.
I agree.
> > +gpio_reg:
> > + lpc_ich_finalize_cell(&lpc_ich_cells[LPC_GPIO], id);
> > + ret = mfd_add_devices(&dev->dev, 0,
> > &lpc_ich_cells[LPC_GPIO],
> > + 1, NULL, 0);
> > + if (!ret)
> > + cells++;
> > +
> So if there is an error, ret < 0, correct ?
>
> > + if (acpi_conflict)
> > + dev_info(&dev->dev, "ACPI resource conflicts found;
> > "
> > + "consider using
> > acpi_enforce_resources=lax?\n");
> > +
> > + if (cells)
> > + return 0;
> > + else
> > + return -ENODEV;
>
> If the above is true, you can just return ret, and you don't need the
> cells variable. Or, even better, move the acpi warning above the call
> to mfd_add_devices().
The cells variable isn't strictly necessary when we're only dealing
with one cell registration, as we have if only looking at the lpc_ich
and gpio-ich patches. The iTCO_wdt patch adds a second call to
mfd_add_devices, so when we return we're interested if either of the
calls succeeded. This was intended to be a forward thinking
implementation, but I have no qualms about simplifying it in the
initial lpc_ich patch.
On Tue, 2012-02-07 at 14:59 -0500, Aaron Sierra wrote:
> This patch converts the iTCO_wdt driver to use the multi-function device
> driver model. It uses resources discovered by the lpc_ich driver, so that
> it no longer does its own PCI scanning.
>
> The driver has also been modernized to use pr_info and the like.
>
> Signed-off-by: Aaron Sierra <[email protected]>
> Signed-off-by: Guenter Roeck <[email protected]>
Hi Aaron,
Couple of comments below.
Thanks,
Guenter
> ---
> drivers/mfd/Kconfig | 3 +-
> drivers/mfd/lpc_ich.c | 199 +++++++++----
> drivers/watchdog/Kconfig | 1 +
> drivers/watchdog/iTCO_vendor.h | 6 +-
> drivers/watchdog/iTCO_vendor_support.c | 43 ++--
> drivers/watchdog/iTCO_wdt.c | 514 ++++++--------------------------
> include/linux/mfd/lpc_ich.h | 7 +
> 7 files changed, 257 insertions(+), 516 deletions(-)
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index f581e59..428e0a2 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -731,7 +731,8 @@ config LPC_ICH
> help
> The LPC bridge function of the Intel ICH provides support for
> many functional units. This driver provides needed support for
> - other drivers to control these functions, currently GPIO.
> + other drivers to control these functions, currently GPIO and
> + watchdog.
>
> config MFD_RDC321X
> tristate "Support for RDC-R321x southbridge"
> diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
> index 3f159fc..eb37b05 100644
> --- a/drivers/mfd/lpc_ich.c
> +++ b/drivers/mfd/lpc_ich.c
> @@ -63,15 +63,43 @@
> #define ACPIBASE 0x40
> #define ACPIBASE_GPE_OFF 0x20
> #define ACPIBASE_GPE_END 0x2f
> +#define ACPIBASE_SMI_OFF 0x30
> +#define ACPIBASE_SMI_END 0x33
> +#define ACPIBASE_TCO_OFF 0x60
> +#define ACPIBASE_TCO_END 0x7f
> #define ACPICTRL 0x44
>
> +#define ACPIBASE_GCS_OFF 0x3410
> +#define ACPIBASE_GCS_END 0x3414
> +
> #define GPIOBASE 0x48
> #define GPIOCTRL 0x4C
> #define GPIOBASE_IO_SIZE 0x80
>
> +#define RCBABASE 0xf0
> +
> +#define wdt_io_res(i) wdt_res(0, i)
> +#define wdt_mem_res(i) wdt_res(ICH_RES_MEM_OFF, i)
> +#define wdt_res(b, i) (&wdt_ich_res[(b) + (i)])
> +
> static int lpc_ich_acpi_save = -1;
> static int lpc_ich_gpio_save = -1;
>
> +static struct resource wdt_ich_res[] = {
> + /* TCO */
> + {
> + .flags = IORESOURCE_IO,
> + },
> + /* SMI */
> + {
> + .flags = IORESOURCE_IO,
> + },
> + /* GCS */
> + {
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> static struct resource gpio_ich_res[] = {
> /* BASE */
> {
> @@ -84,10 +112,16 @@ static struct resource gpio_ich_res[] = {
> };
>
> enum lpc_cells {
> - LPC_GPIO = 0,
> + LPC_WDT = 0,
> + LPC_GPIO,
> };
>
> static struct mfd_cell lpc_ich_cells[] = {
> + [LPC_WDT] = {
> + .name = "iTCO_wdt",
> + .num_resources = ARRAY_SIZE(wdt_ich_res),
> + .resources = wdt_ich_res,
> + },
> [LPC_GPIO] = {
> .name = "gpio_ich",
> .num_resources = ARRAY_SIZE(gpio_ich_res),
> @@ -158,64 +192,64 @@ enum lpc_chipsets {
> };
>
> struct lpc_ich_info lpc_chipset_info[] __devinitdata = {
> - [LPC_ICH] = {"ICH", 0},
> - [LPC_ICH0] = {"ICH0", 0},
> - [LPC_ICH2] = {"ICH2", 0},
> - [LPC_ICH2M] = {"ICH2-M", 0},
> - [LPC_ICH3] = {"ICH3-S", 0},
> - [LPC_ICH3M] = {"ICH3-M", 0},
> - [LPC_ICH4] = {"ICH4", 0},
> - [LPC_ICH4M] = {"ICH4-M", 0},
> - [LPC_CICH] = {"C-ICH", 0},
> - [LPC_ICH5] = {"ICH5 or ICH5R", 0},
> - [LPC_6300ESB] = {"6300ESB", 0},
> - [LPC_ICH6] = {"ICH6 or ICH6R", 0x0601},
> - [LPC_ICH6M] = {"ICH6-M", 0x0601},
> - [LPC_ICH6W] = {"ICH6W or ICH6RW", 0x0601},
> - [LPC_631XESB] = {"631xESB/632xESB", 0x0601},
> - [LPC_ICH7] = {"ICH7 or ICH7R", 0x0701},
> - [LPC_ICH7DH] = {"ICH7DH", 0x0701},
> - [LPC_ICH7M] = {"ICH7-M or ICH7-U", 0x0701},
> - [LPC_ICH7MDH] = {"ICH7-M DH", 0x0701},
> - [LPC_NM10] = {"NM10", 0},
> - [LPC_ICH8] = {"ICH8 or ICH8R", 0x0701},
> - [LPC_ICH8DH] = {"ICH8DH", 0x0701},
> - [LPC_ICH8DO] = {"ICH8DO", 0x0701},
> - [LPC_ICH8M] = {"ICH8M", 0x0701},
> - [LPC_ICH8ME] = {"ICH8M-E", 0x0701},
> - [LPC_ICH9] = {"ICH9", 0x0801},
> - [LPC_ICH9R] = {"ICH9R", 0x0801},
> - [LPC_ICH9DH] = {"ICH9DH", 0x0801},
> - [LPC_ICH9DO] = {"ICH9DO", 0x0801},
> - [LPC_ICH9M] = {"ICH9M", 0x0801},
> - [LPC_ICH9ME] = {"ICH9M-E", 0x0801},
> - [LPC_ICH10] = {"ICH10", 0x0a11},
> - [LPC_ICH10R] = {"ICH10R", 0x0a11},
> - [LPC_ICH10D] = {"ICH10D", 0x0a01},
> - [LPC_ICH10DO] = {"ICH10DO", 0x0a01},
> - [LPC_PCH] = {"PCH Desktop Full Featured", 0x0501},
> - [LPC_PCHM] = {"PCH Mobile Full Featured", 0x0501},
> - [LPC_P55] = {"P55", 0x0501},
> - [LPC_PM55] = {"PM55", 0x0501},
> - [LPC_H55] = {"H55", 0x0501},
> - [LPC_QM57] = {"QM57", 0x0501},
> - [LPC_H57] = {"H57", 0x0501},
> - [LPC_HM55] = {"HM55", 0x0501},
> - [LPC_Q57] = {"Q57", 0x0501},
> - [LPC_HM57] = {"HM57", 0x0501},
> - [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",0x0501},
> - [LPC_QS57] = {"QS57", 0x0501},
> - [LPC_3400] = {"3400", 0x0501},
> - [LPC_3420] = {"3420", 0x0501},
> - [LPC_3450] = {"3450", 0x0501},
> - [LPC_EP80579] = {"EP80579", 0},
> - [LPC_CPT] = {"Cougar Point", 0x0501},
> - [LPC_CPTD] = {"Cougar Point Desktop", 0x0501},
> - [LPC_CPTM] = {"Cougar Point Mobile", 0x0501},
> - [LPC_PBG] = {"Patsburg", 0},
> - [LPC_DH89XXCC] = {"DH89xxCC", 0},
> - [LPC_PPT] = {"Panther Point", 0},
> - [LPC_LPT] = {"Lynx Point", 0},
> + [LPC_ICH] = {"ICH", 1, 0},
> + [LPC_ICH0] = {"ICH0", 1, 0},
> + [LPC_ICH2] = {"ICH2", 1, 0},
> + [LPC_ICH2M] = {"ICH2-M", 1, 0},
> + [LPC_ICH3] = {"ICH3-S", 1, 0},
> + [LPC_ICH3M] = {"ICH3-M", 1, 0},
> + [LPC_ICH4] = {"ICH4", 1, 0},
> + [LPC_ICH4M] = {"ICH4-M", 1, 0},
> + [LPC_CICH] = {"C-ICH", 1, 0},
> + [LPC_ICH5] = {"ICH5 or ICH5R", 1, 0},
> + [LPC_6300ESB] = {"6300ESB", 1, 0},
> + [LPC_ICH6] = {"ICH6 or ICH6R", 2, 0x0601},
> + [LPC_ICH6M] = {"ICH6-M", 2, 0x0601},
> + [LPC_ICH6W] = {"ICH6W or ICH6RW", 2, 0x0601},
> + [LPC_631XESB] = {"631xESB/632xESB", 2, 0x0601},
> + [LPC_ICH7] = {"ICH7 or ICH7R", 2, 0x0701},
> + [LPC_ICH7DH] = {"ICH7DH", 2, 0x0701},
> + [LPC_ICH7M] = {"ICH7-M or ICH7-U", 2, 0x0701},
> + [LPC_ICH7MDH] = {"ICH7-M DH", 2, 0x0701},
> + [LPC_NM10] = {"NM10", 2, 0},
> + [LPC_ICH8] = {"ICH8 or ICH8R", 2, 0x0701},
> + [LPC_ICH8DH] = {"ICH8DH", 2, 0x0701},
> + [LPC_ICH8DO] = {"ICH8DO", 2, 0x0701},
> + [LPC_ICH8M] = {"ICH8M", 2, 0x0701},
> + [LPC_ICH8ME] = {"ICH8M-E", 2, 0x0701},
> + [LPC_ICH9] = {"ICH9", 2, 0x0801},
> + [LPC_ICH9R] = {"ICH9R", 2, 0x0801},
> + [LPC_ICH9DH] = {"ICH9DH", 2, 0x0801},
> + [LPC_ICH9DO] = {"ICH9DO", 2, 0x0801},
> + [LPC_ICH9M] = {"ICH9M", 2, 0x0801},
> + [LPC_ICH9ME] = {"ICH9M-E", 2, 0x0801},
> + [LPC_ICH10] = {"ICH10", 2, 0x0a11},
> + [LPC_ICH10R] = {"ICH10R", 2, 0x0a11},
> + [LPC_ICH10D] = {"ICH10D", 2, 0x0a01},
> + [LPC_ICH10DO] = {"ICH10DO", 2, 0x0a01},
> + [LPC_PCH] = {"PCH Desktop Full Featured", 2, 0x0501},
> + [LPC_PCHM] = {"PCH Mobile Full Featured", 2, 0x0501},
> + [LPC_P55] = {"P55", 2, 0x0501},
> + [LPC_PM55] = {"PM55", 2, 0x0501},
> + [LPC_H55] = {"H55", 2, 0x0501},
> + [LPC_QM57] = {"QM57", 2, 0x0501},
> + [LPC_H57] = {"H57", 2, 0x0501},
> + [LPC_HM55] = {"HM55", 2, 0x0501},
> + [LPC_Q57] = {"Q57", 2, 0x0501},
> + [LPC_HM57] = {"HM57", 2, 0x0501},
> + [LPC_PCHMSFF] = {"PCH Mobile SFF Full Featured",2, 0x0501},
> + [LPC_QS57] = {"QS57", 2, 0x0501},
> + [LPC_3400] = {"3400", 2, 0x0501},
> + [LPC_3420] = {"3420", 2, 0x0501},
> + [LPC_3450] = {"3450", 2, 0x0501},
> + [LPC_EP80579] = {"EP80579", 2, 0},
> + [LPC_CPT] = {"Cougar Point", 2, 0x0501},
> + [LPC_CPTD] = {"Cougar Point Desktop", 2, 0x0501},
> + [LPC_CPTM] = {"Cougar Point Mobile", 2, 0x0501},
> + [LPC_PBG] = {"Patsburg", 2, 0},
> + [LPC_DH89XXCC] = {"DH89xxCC", 2, 0},
> + [LPC_PPT] = {"Panther Point", 2, 0},
> + [LPC_LPT] = {"Lynx Point", 2, 0},
> };
>
> /*
> @@ -429,11 +463,52 @@ static int __devinit lpc_ich_probe(struct pci_dev *dev,
> acpi_conflict++;
> }
>
> + wdt_io_res(ICH_RES_IO_TCO)->start = base_addr + ACPIBASE_TCO_OFF;
> + wdt_io_res(ICH_RES_IO_TCO)->end = base_addr + ACPIBASE_TCO_END;
> + ret = acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_TCO));
> + if (ret) {
> + acpi_conflict++;
> + goto pm_done;
> + }
> +
> + wdt_io_res(ICH_RES_IO_SMI)->start = base_addr + ACPIBASE_SMI_OFF;
> + wdt_io_res(ICH_RES_IO_SMI)->end = base_addr + ACPIBASE_SMI_END;
> + ret = acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_SMI));
> + if (ret) {
> + acpi_conflict++;
> + goto pm_done;
> + }
> +
I'll have to look into the merged code, but doesn't this mean that iTCO
resource requirement conflicts impact GPIO resource requirements ? If
yes, is it possible to keep those separate ?
> /* Enable LPC ACPI space */
> pci_read_config_byte(dev, ACPICTRL, ®_save);
> pci_write_config_byte(dev, ACPICTRL, reg_save | 0x10);
> lpc_ich_acpi_save = (int)reg_save;
>
> + /*
> + * Get the Memory-Mapped GCS register. To get access to it
> + * we have to read RCBA from PCI Config space 0xf0 and use
> + * it as base. GCS = RCBA + ICH6_GCS(0x3410).
> + */
> + if (lpc_chipset_info[id->driver_data].iTCO_version == 2) {
> + pci_read_config_dword(dev, RCBABASE, &base_addr_cfg);
> + base_addr = base_addr_cfg & 0xffffc000;
> + if (base_addr_cfg & 1) {
> + wdt_mem_res(ICH_RES_MEM_GCS)->start = base_addr +
> + ACPIBASE_GCS_OFF;
> + wdt_mem_res(ICH_RES_MEM_GCS)->end = base_addr +
> + ACPIBASE_GCS_END;
> + } else {
> + pr_err("RCBA is disabled by hardware/BIOS, "
> + "device disabled\n");
> + }
> + }
> +
> + lpc_ich_finalize_cell(&lpc_ich_cells[LPC_WDT], id);
> + ret = mfd_add_devices(&dev->dev, 0, &lpc_ich_cells[LPC_WDT],
> + 1, NULL, 0);
> + if (!ret)
> + cells++;
> +
Ah, guess that explains the cell variable.
> pm_done:
> /* Setup GPIO base register */
> pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
> @@ -481,8 +556,10 @@ static void __devexit lpc_ich_remove(struct pci_dev *dev)
> {
> mfd_remove_devices(&dev->dev);
>
> - pci_write_config_byte(dev, GPIOCTRL, lpc_ich_gpio_save);
> - pci_write_config_byte(dev, ACPICTRL, lpc_ich_acpi_save);
> + if (lpc_ich_gpio_save > 0)
> + pci_write_config_byte(dev, GPIOCTRL, (u8)lpc_ich_gpio_save);
> + if (lpc_ich_acpi_save > 0)
> + pci_write_config_byte(dev, ACPICTRL, (u8)lpc_ich_acpi_save);
> }
>
> static struct pci_driver lpc_ich_driver = {
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 877b107..c3a4d7f 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -557,6 +557,7 @@ config INTEL_SCU_WATCHDOG
> config ITCO_WDT
> tristate "Intel TCO Timer/Watchdog"
> depends on (X86 || IA64) && PCI
> + select LPC_ICH
> ---help---
> Hardware driver for the intel TCO timer based watchdog devices.
> These drivers are included in the Intel 82801 I/O Controller
> diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h
> index 9e27e64..3c57b45 100644
> --- a/drivers/watchdog/iTCO_vendor.h
> +++ b/drivers/watchdog/iTCO_vendor.h
> @@ -1,8 +1,8 @@
> /* iTCO Vendor Specific Support hooks */
> #ifdef CONFIG_ITCO_VENDOR_SUPPORT
> -extern void iTCO_vendor_pre_start(unsigned long, unsigned int);
> -extern void iTCO_vendor_pre_stop(unsigned long);
> -extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int);
> +extern void iTCO_vendor_pre_start(struct resource *, unsigned int);
> +extern void iTCO_vendor_pre_stop(struct resource *);
> +extern void iTCO_vendor_pre_keepalive(struct resource *, unsigned int);
> extern void iTCO_vendor_pre_set_heartbeat(unsigned int);
> extern int iTCO_vendor_check_noreboot_on(void);
> #else
> diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c
> index 481d1ad..3b80d6f 100644
> --- a/drivers/watchdog/iTCO_vendor_support.c
> +++ b/drivers/watchdog/iTCO_vendor_support.c
> @@ -34,11 +34,6 @@
>
> #include "iTCO_vendor.h"
>
> -/* iTCO defines */
> -#define SMI_EN (acpibase + 0x30) /* SMI Control and Enable Register */
> -#define TCOBASE (acpibase + 0x60) /* TCO base address */
> -#define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */
> -
> /* List of vendor support modes */
> /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */
> #define SUPERMICRO_OLD_BOARD 1
> @@ -81,24 +76,24 @@ MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default="
> * 20.6 seconds.
> */
>
> -static void supermicro_old_pre_start(unsigned long acpibase)
> +static void supermicro_old_pre_start(struct resource *smires)
> {
> unsigned long val32;
>
> /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */
> - val32 = inl(SMI_EN);
> + val32 = inl(smires->start);
> val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
> - outl(val32, SMI_EN); /* Needed to activate watchdog */
> + outl(val32, smires->start); /* Needed to activate watchdog */
> }
>
> -static void supermicro_old_pre_stop(unsigned long acpibase)
> +static void supermicro_old_pre_stop(struct resource *smires)
> {
> unsigned long val32;
>
> /* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */
> - val32 = inl(SMI_EN);
> + val32 = inl(smires->start);
> val32 |= 0x00002000; /* Turn on SMI clearing watchdog */
> - outl(val32, SMI_EN); /* Needed to deactivate watchdog */
> + outl(val32, smires->start); /* Needed to deactivate watchdog */
> }
>
> /*
> @@ -269,66 +264,66 @@ static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat)
> * Don't use this fix if you don't need to!!!
> */
>
> -static void broken_bios_start(unsigned long acpibase)
> +static void broken_bios_start(struct resource *smires)
> {
> unsigned long val32;
>
> - val32 = inl(SMI_EN);
> + val32 = inl(smires->start);
> /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI#
> Bit 0: GBL_SMI_EN -> 0 = No SMI# will be generated by ICH. */
> val32 &= 0xffffdffe;
> - outl(val32, SMI_EN);
> + outl(val32, smires->start);
> }
>
> -static void broken_bios_stop(unsigned long acpibase)
> +static void broken_bios_stop(struct resource *smires)
> {
> unsigned long val32;
>
> - val32 = inl(SMI_EN);
> + val32 = inl(smires->start);
> /* Bit 13: TCO_EN -> 1 = Enables TCO logic generating an SMI#
> Bit 0: GBL_SMI_EN -> 1 = Turn global SMI on again. */
> val32 |= 0x00002001;
> - outl(val32, SMI_EN);
> + outl(val32, smires->start);
> }
>
> /*
> * Generic Support Functions
> */
>
> -void iTCO_vendor_pre_start(unsigned long acpibase,
> +void iTCO_vendor_pre_start(struct resource *smires,
> unsigned int heartbeat)
> {
> switch (vendorsupport) {
> case SUPERMICRO_OLD_BOARD:
> - supermicro_old_pre_start(acpibase);
> + supermicro_old_pre_start(smires);
> break;
> case SUPERMICRO_NEW_BOARD:
> supermicro_new_pre_start(heartbeat);
> break;
> case BROKEN_BIOS:
> - broken_bios_start(acpibase);
> + broken_bios_start(smires);
> break;
> }
> }
> EXPORT_SYMBOL(iTCO_vendor_pre_start);
>
> -void iTCO_vendor_pre_stop(unsigned long acpibase)
> +void iTCO_vendor_pre_stop(struct resource *smires)
> {
> switch (vendorsupport) {
> case SUPERMICRO_OLD_BOARD:
> - supermicro_old_pre_stop(acpibase);
> + supermicro_old_pre_stop(smires);
> break;
> case SUPERMICRO_NEW_BOARD:
> supermicro_new_pre_stop();
> break;
> case BROKEN_BIOS:
> - broken_bios_stop(acpibase);
> + broken_bios_stop(smires);
> break;
> }
> }
> EXPORT_SYMBOL(iTCO_vendor_pre_stop);
>
> -void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat)
> +void iTCO_vendor_pre_keepalive(struct resource *smires, unsigned int heartbeat)
> {
> if (vendorsupport == SUPERMICRO_NEW_BOARD)
> supermicro_new_pre_set_heartbeat(heartbeat);
> diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
> index bdf401b..7a413f3 100644
> --- a/drivers/watchdog/iTCO_wdt.c
> +++ b/drivers/watchdog/iTCO_wdt.c
> @@ -46,7 +46,7 @@
> /* Module and version information */
> #define DRV_NAME "iTCO_wdt"
> #define DRV_VERSION "1.07"
> -#define PFX DRV_NAME ": "
> +#define pr_fmt(fmt) DRV_NAME ": " fmt
>
> /* Includes */
> #include <linux/module.h> /* For module specific items */
> @@ -65,316 +65,16 @@
> #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
> #include <linux/uaccess.h> /* For copy_to_user/put_user/... */
> #include <linux/io.h> /* For inb/outb/... */
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/lpc_ich.h>
>
> #include "iTCO_vendor.h"
>
> -/* TCO related info */
> -enum iTCO_chipsets {
> - TCO_ICH = 0, /* ICH */
> - TCO_ICH0, /* ICH0 */
> - TCO_ICH2, /* ICH2 */
> - TCO_ICH2M, /* ICH2-M */
> - TCO_ICH3, /* ICH3-S */
> - TCO_ICH3M, /* ICH3-M */
> - TCO_ICH4, /* ICH4 */
> - TCO_ICH4M, /* ICH4-M */
> - TCO_CICH, /* C-ICH */
> - TCO_ICH5, /* ICH5 & ICH5R */
> - TCO_6300ESB, /* 6300ESB */
> - TCO_ICH6, /* ICH6 & ICH6R */
> - TCO_ICH6M, /* ICH6-M */
> - TCO_ICH6W, /* ICH6W & ICH6RW */
> - TCO_631XESB, /* 631xESB/632xESB */
> - TCO_ICH7, /* ICH7 & ICH7R */
> - TCO_ICH7DH, /* ICH7DH */
> - TCO_ICH7M, /* ICH7-M & ICH7-U */
> - TCO_ICH7MDH, /* ICH7-M DH */
> - TCO_NM10, /* NM10 */
> - TCO_ICH8, /* ICH8 & ICH8R */
> - TCO_ICH8DH, /* ICH8DH */
> - TCO_ICH8DO, /* ICH8DO */
> - TCO_ICH8M, /* ICH8M */
> - TCO_ICH8ME, /* ICH8M-E */
> - TCO_ICH9, /* ICH9 */
> - TCO_ICH9R, /* ICH9R */
> - TCO_ICH9DH, /* ICH9DH */
> - TCO_ICH9DO, /* ICH9DO */
> - TCO_ICH9M, /* ICH9M */
> - TCO_ICH9ME, /* ICH9M-E */
> - TCO_ICH10, /* ICH10 */
> - TCO_ICH10R, /* ICH10R */
> - TCO_ICH10D, /* ICH10D */
> - TCO_ICH10DO, /* ICH10DO */
> - TCO_PCH, /* PCH Desktop Full Featured */
> - TCO_PCHM, /* PCH Mobile Full Featured */
> - TCO_P55, /* P55 */
> - TCO_PM55, /* PM55 */
> - TCO_H55, /* H55 */
> - TCO_QM57, /* QM57 */
> - TCO_H57, /* H57 */
> - TCO_HM55, /* HM55 */
> - TCO_Q57, /* Q57 */
> - TCO_HM57, /* HM57 */
> - TCO_PCHMSFF, /* PCH Mobile SFF Full Featured */
> - TCO_QS57, /* QS57 */
> - TCO_3400, /* 3400 */
> - TCO_3420, /* 3420 */
> - TCO_3450, /* 3450 */
> - TCO_EP80579, /* EP80579 */
> - TCO_CPT, /* Cougar Point */
> - TCO_CPTD, /* Cougar Point Desktop */
> - TCO_CPTM, /* Cougar Point Mobile */
> - TCO_PBG, /* Patsburg */
> - TCO_DH89XXCC, /* DH89xxCC */
> - TCO_PPT, /* Panther Point */
> - TCO_LPT, /* Lynx Point */
> -};
> -
> -static struct {
> - char *name;
> - unsigned int iTCO_version;
> -} iTCO_chipset_info[] __devinitdata = {
> - {"ICH", 1},
> - {"ICH0", 1},
> - {"ICH2", 1},
> - {"ICH2-M", 1},
> - {"ICH3-S", 1},
> - {"ICH3-M", 1},
> - {"ICH4", 1},
> - {"ICH4-M", 1},
> - {"C-ICH", 1},
> - {"ICH5 or ICH5R", 1},
> - {"6300ESB", 1},
> - {"ICH6 or ICH6R", 2},
> - {"ICH6-M", 2},
> - {"ICH6W or ICH6RW", 2},
> - {"631xESB/632xESB", 2},
> - {"ICH7 or ICH7R", 2},
> - {"ICH7DH", 2},
> - {"ICH7-M or ICH7-U", 2},
> - {"ICH7-M DH", 2},
> - {"NM10", 2},
> - {"ICH8 or ICH8R", 2},
> - {"ICH8DH", 2},
> - {"ICH8DO", 2},
> - {"ICH8M", 2},
> - {"ICH8M-E", 2},
> - {"ICH9", 2},
> - {"ICH9R", 2},
> - {"ICH9DH", 2},
> - {"ICH9DO", 2},
> - {"ICH9M", 2},
> - {"ICH9M-E", 2},
> - {"ICH10", 2},
> - {"ICH10R", 2},
> - {"ICH10D", 2},
> - {"ICH10DO", 2},
> - {"PCH Desktop Full Featured", 2},
> - {"PCH Mobile Full Featured", 2},
> - {"P55", 2},
> - {"PM55", 2},
> - {"H55", 2},
> - {"QM57", 2},
> - {"H57", 2},
> - {"HM55", 2},
> - {"Q57", 2},
> - {"HM57", 2},
> - {"PCH Mobile SFF Full Featured", 2},
> - {"QS57", 2},
> - {"3400", 2},
> - {"3420", 2},
> - {"3450", 2},
> - {"EP80579", 2},
> - {"Cougar Point", 2},
> - {"Cougar Point Desktop", 2},
> - {"Cougar Point Mobile", 2},
> - {"Patsburg", 2},
> - {"DH89xxCC", 2},
> - {"Panther Point", 2},
> - {"Lynx Point", 2},
> - {NULL, 0}
> -};
> -
> -/*
> - * This data only exists for exporting the supported PCI ids
> - * via MODULE_DEVICE_TABLE. We do not actually register a
> - * pci_driver, because the I/O Controller Hub has also other
> - * functions that probably will be registered by other drivers.
> - */
> -static DEFINE_PCI_DEVICE_TABLE(iTCO_wdt_pci_tbl) = {
> - { PCI_VDEVICE(INTEL, 0x2410), TCO_ICH},
> - { PCI_VDEVICE(INTEL, 0x2420), TCO_ICH0},
> - { PCI_VDEVICE(INTEL, 0x2440), TCO_ICH2},
> - { PCI_VDEVICE(INTEL, 0x244c), TCO_ICH2M},
> - { PCI_VDEVICE(INTEL, 0x2480), TCO_ICH3},
> - { PCI_VDEVICE(INTEL, 0x248c), TCO_ICH3M},
> - { PCI_VDEVICE(INTEL, 0x24c0), TCO_ICH4},
> - { PCI_VDEVICE(INTEL, 0x24cc), TCO_ICH4M},
> - { PCI_VDEVICE(INTEL, 0x2450), TCO_CICH},
> - { PCI_VDEVICE(INTEL, 0x24d0), TCO_ICH5},
> - { PCI_VDEVICE(INTEL, 0x25a1), TCO_6300ESB},
> - { PCI_VDEVICE(INTEL, 0x2640), TCO_ICH6},
> - { PCI_VDEVICE(INTEL, 0x2641), TCO_ICH6M},
> - { PCI_VDEVICE(INTEL, 0x2642), TCO_ICH6W},
> - { PCI_VDEVICE(INTEL, 0x2670), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2671), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2672), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2673), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2674), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2675), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2676), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2677), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2678), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x2679), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267a), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267b), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267c), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267d), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267e), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x267f), TCO_631XESB},
> - { PCI_VDEVICE(INTEL, 0x27b8), TCO_ICH7},
> - { PCI_VDEVICE(INTEL, 0x27b0), TCO_ICH7DH},
> - { PCI_VDEVICE(INTEL, 0x27b9), TCO_ICH7M},
> - { PCI_VDEVICE(INTEL, 0x27bd), TCO_ICH7MDH},
> - { PCI_VDEVICE(INTEL, 0x27bc), TCO_NM10},
> - { PCI_VDEVICE(INTEL, 0x2810), TCO_ICH8},
> - { PCI_VDEVICE(INTEL, 0x2812), TCO_ICH8DH},
> - { PCI_VDEVICE(INTEL, 0x2814), TCO_ICH8DO},
> - { PCI_VDEVICE(INTEL, 0x2815), TCO_ICH8M},
> - { PCI_VDEVICE(INTEL, 0x2811), TCO_ICH8ME},
> - { PCI_VDEVICE(INTEL, 0x2918), TCO_ICH9},
> - { PCI_VDEVICE(INTEL, 0x2916), TCO_ICH9R},
> - { PCI_VDEVICE(INTEL, 0x2912), TCO_ICH9DH},
> - { PCI_VDEVICE(INTEL, 0x2914), TCO_ICH9DO},
> - { PCI_VDEVICE(INTEL, 0x2919), TCO_ICH9M},
> - { PCI_VDEVICE(INTEL, 0x2917), TCO_ICH9ME},
> - { PCI_VDEVICE(INTEL, 0x3a18), TCO_ICH10},
> - { PCI_VDEVICE(INTEL, 0x3a16), TCO_ICH10R},
> - { PCI_VDEVICE(INTEL, 0x3a1a), TCO_ICH10D},
> - { PCI_VDEVICE(INTEL, 0x3a14), TCO_ICH10DO},
> - { PCI_VDEVICE(INTEL, 0x3b00), TCO_PCH},
> - { PCI_VDEVICE(INTEL, 0x3b01), TCO_PCHM},
> - { PCI_VDEVICE(INTEL, 0x3b02), TCO_P55},
> - { PCI_VDEVICE(INTEL, 0x3b03), TCO_PM55},
> - { PCI_VDEVICE(INTEL, 0x3b06), TCO_H55},
> - { PCI_VDEVICE(INTEL, 0x3b07), TCO_QM57},
> - { PCI_VDEVICE(INTEL, 0x3b08), TCO_H57},
> - { PCI_VDEVICE(INTEL, 0x3b09), TCO_HM55},
> - { PCI_VDEVICE(INTEL, 0x3b0a), TCO_Q57},
> - { PCI_VDEVICE(INTEL, 0x3b0b), TCO_HM57},
> - { PCI_VDEVICE(INTEL, 0x3b0d), TCO_PCHMSFF},
> - { PCI_VDEVICE(INTEL, 0x3b0f), TCO_QS57},
> - { PCI_VDEVICE(INTEL, 0x3b12), TCO_3400},
> - { PCI_VDEVICE(INTEL, 0x3b14), TCO_3420},
> - { PCI_VDEVICE(INTEL, 0x3b16), TCO_3450},
> - { PCI_VDEVICE(INTEL, 0x5031), TCO_EP80579},
> - { PCI_VDEVICE(INTEL, 0x1c41), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c42), TCO_CPTD},
> - { PCI_VDEVICE(INTEL, 0x1c43), TCO_CPTM},
> - { PCI_VDEVICE(INTEL, 0x1c44), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c45), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c46), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c47), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c48), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c49), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4a), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4b), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4c), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4d), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4e), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c4f), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c50), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c51), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c52), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c53), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c54), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c55), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c56), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c57), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c58), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c59), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5a), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5b), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5c), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5d), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5e), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1c5f), TCO_CPT},
> - { PCI_VDEVICE(INTEL, 0x1d40), TCO_PBG},
> - { PCI_VDEVICE(INTEL, 0x1d41), TCO_PBG},
> - { PCI_VDEVICE(INTEL, 0x2310), TCO_DH89XXCC},
> - { PCI_VDEVICE(INTEL, 0x1e40), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e41), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e42), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e43), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e44), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e45), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e46), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e47), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e48), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e49), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4a), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4b), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4c), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4d), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4e), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e4f), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e50), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e51), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e52), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e53), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e54), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e55), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e56), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e57), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e58), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e59), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5a), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5b), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5c), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5d), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5e), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x1e5f), TCO_PPT},
> - { PCI_VDEVICE(INTEL, 0x8c40), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c41), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c42), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c43), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c44), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c45), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c46), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c47), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c48), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c49), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4a), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4b), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4c), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4d), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4e), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c4f), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c50), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c51), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c52), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c53), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c54), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c55), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c56), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c57), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c58), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c59), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5a), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5b), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5c), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5d), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5e), TCO_LPT},
> - { PCI_VDEVICE(INTEL, 0x8c5f), TCO_LPT},
> - { 0, }, /* End of list */
> -};
> -MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl);
> -
> /* Address definitions for the TCO */
> /* TCO base address */
> -#define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60)
> +#define TCOBASE iTCO_wdt_private.tco_res->start
You might want to put this expression in ().
> /* SMI Control and Enable Register */
> -#define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30)
> +#define SMI_EN iTCO_wdt_private.smi_res->start
>
Same here.
> #define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */
> #define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */
> @@ -392,19 +92,18 @@ static char expect_release;
> static struct { /* this is private data for the iTCO_wdt device */
> /* TCO version/generation */
> unsigned int iTCO_version;
> - /* The device's ACPIBASE address (TCOBASE = ACPIBASE+0x60) */
> - unsigned long ACPIBASE;
> + struct resource *tco_res;
> + struct resource *smi_res;
> + struct resource *gcs_res;
> /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/
> unsigned long __iomem *gcs;
> /* the lock for io operations */
> spinlock_t io_lock;
> + struct platform_device *dev;
> /* the PCI-device */
> struct pci_dev *pdev;
> } iTCO_wdt_private;
>
> -/* the watchdog platform device */
> -static struct platform_device *iTCO_wdt_platform_device;
> -
> /* module parameters */
> #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
> static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
> @@ -484,12 +183,12 @@ static int iTCO_wdt_start(void)
>
> spin_lock(&iTCO_wdt_private.io_lock);
>
> - iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat);
> + iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, heartbeat);
>
> /* disable chipset's NO_REBOOT bit */
> if (iTCO_wdt_unset_NO_REBOOT_bit()) {
> spin_unlock(&iTCO_wdt_private.io_lock);
> - printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, "
> + pr_err("failed to reset NO_REBOOT flag, "
> "reboot disabled by hardware/BIOS\n");
> return -EIO;
> }
> @@ -519,7 +218,7 @@ static int iTCO_wdt_stop(void)
>
> spin_lock(&iTCO_wdt_private.io_lock);
>
> - iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE);
> + iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res);
>
> /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
> val = inw(TCO1_CNT);
> @@ -541,7 +240,7 @@ static int iTCO_wdt_keepalive(void)
> {
> spin_lock(&iTCO_wdt_private.io_lock);
>
> - iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat);
> + iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, heartbeat);
>
> /* Reload the timer by writing to the TCO Timer Counter register */
> if (iTCO_wdt_private.iTCO_version == 2)
> @@ -661,8 +360,7 @@ static int iTCO_wdt_release(struct inode *inode, struct file *file)
> if (expect_release == 42) {
> iTCO_wdt_stop();
> } else {
> - printk(KERN_CRIT PFX
> - "Unexpected close, not stopping watchdog!\n");
> + pr_crit("Unexpected close, not stopping watchdog!\n");
> iTCO_wdt_keepalive();
> }
> clear_bit(0, &is_active);
> @@ -787,51 +485,71 @@ static struct miscdevice iTCO_wdt_miscdev = {
> * Init & exit routines
> */
>
> -static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
> - const struct pci_device_id *ent, struct platform_device *dev)
> +static void __devexit iTCO_wdt_cleanup(void)
> +{
> + /* Stop the timer before we leave */
> + if (!nowayout)
> + iTCO_wdt_stop();
> +
> + /* Deregister */
> + misc_deregister(&iTCO_wdt_miscdev);
> + release_resource(iTCO_wdt_private.tco_res);
> + release_resource(iTCO_wdt_private.smi_res);
> + release_resource(iTCO_wdt_private.gcs_res);
> + if (iTCO_wdt_private.iTCO_version == 2)
> + iounmap(iTCO_wdt_private.gcs);
WOuld it make sense to reset gcs to NULL as well ?
> + iTCO_wdt_private.tco_res = NULL;
> + iTCO_wdt_private.smi_res = NULL;
> + iTCO_wdt_private.gcs_res = NULL;
> +}
> +
> +static int __devinit iTCO_wdt_probe(struct platform_device *dev)
> {
> int ret;
> - u32 base_address;
> - unsigned long RCBA;
> unsigned long val32;
> + struct lpc_ich_info *ich_info = dev->dev.platform_data;
>
> - /*
> - * Find the ACPI/PM base I/O address which is the base
> - * for the TCO registers (TCOBASE=ACPIBASE + 0x60)
> - * ACPIBASE is bits [15:7] from 0x40-0x43
> - */
> - pci_read_config_dword(pdev, 0x40, &base_address);
> - base_address &= 0x0000ff80;
> - if (base_address == 0x00000000) {
> - /* Something's wrong here, ACPIBASE has to be set */
> - printk(KERN_ERR PFX "failed to get TCOBASE address, "
> - "device disabled by hardware/BIOS\n");
> + if (!ich_info)
> + return -ENODEV;
> +
> + spin_lock_init(&iTCO_wdt_private.io_lock);
> +
> + iTCO_wdt_private.tco_res =
> + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO);
> +
> + iTCO_wdt_private.smi_res =
> + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI);
> +
> + iTCO_wdt_private.gcs_res =
> + platform_get_resource(dev, IORESOURCE_MEM, ICH_RES_MEM_GCS);
> +
> + if (!iTCO_wdt_private.tco_res || !iTCO_wdt_private.smi_res ||
> + !iTCO_wdt_private.gcs_res) {
> + pr_info("No device detected.\n");
> return -ENODEV;
> }
> - iTCO_wdt_private.iTCO_version =
> - iTCO_chipset_info[ent->driver_data].iTCO_version;
> - iTCO_wdt_private.ACPIBASE = base_address;
> - iTCO_wdt_private.pdev = pdev;
> -
> - /* Get the Memory-Mapped GCS register, we need it for the
> - NO_REBOOT flag (TCO v2). To get access to it you have to
> - read RCBA from PCI Config space 0xf0 and use it as base.
> - GCS = RCBA + ICH6_GCS(0x3410). */
> +
> + iTCO_wdt_private.iTCO_version = ich_info->iTCO_version;
> + iTCO_wdt_private.dev = dev;
> + iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent);
> +
> + /*
> + * Get the Memory-Mapped GCS register, we need it for the
> + * NO_REBOOT flag (TCO v2).
> + */
> if (iTCO_wdt_private.iTCO_version == 2) {
> - pci_read_config_dword(pdev, 0xf0, &base_address);
> - if ((base_address & 1) == 0) {
> - printk(KERN_ERR PFX "RCBA is disabled by hardware"
> - "/BIOS, device disabled\n");
> - ret = -ENODEV;
> + if (!request_mem_region(iTCO_wdt_private.gcs_res->start,
> + resource_size(iTCO_wdt_private.gcs_res), dev->name)) {
> + ret = -EBUSY;
> goto out;
> }
> - RCBA = base_address & 0xffffc000;
> - iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4);
> + iTCO_wdt_private.gcs = ioremap(iTCO_wdt_private.gcs_res->start,
> + resource_size(iTCO_wdt_private.gcs_res));
> }
>
> /* Check chipset's NO_REBOOT bit */
> if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) {
> - printk(KERN_INFO PFX "unable to reset NO_REBOOT flag, "
> + pr_info("unable to reset NO_REBOOT flag, "
> "device disabled by hardware/BIOS\n");
> ret = -ENODEV; /* Cannot reset NO_REBOOT bit */
> goto out_unmap;
> @@ -841,11 +559,11 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
> iTCO_wdt_set_NO_REBOOT_bit();
>
> /* The TCO logic uses the TCO_EN bit in the SMI_EN register */
> - if (!request_region(SMI_EN, 4, "iTCO_wdt")) {
> - printk(KERN_ERR PFX
> - "I/O address 0x%04lx already in use, "
> + if (!request_region(iTCO_wdt_private.smi_res->start,
> + resource_size(iTCO_wdt_private.smi_res), dev->name)) {
> + pr_err("I/O address 0x%04llx already in use, "
> "device disabled\n", SMI_EN);
> - ret = -EIO;
> + ret = -EBUSY;
> goto out_unmap;
> }
> if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) {
> @@ -855,20 +573,16 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
> outl(val32, SMI_EN);
> }
>
> - /* The TCO I/O registers reside in a 32-byte range pointed to
> - by the TCOBASE value */
> - if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) {
> - printk(KERN_ERR PFX "I/O address 0x%04lx already in use "
> - "device disabled\n", TCOBASE);
> - ret = -EIO;
> + if (!request_region(iTCO_wdt_private.tco_res->start,
> + resource_size(iTCO_wdt_private.tco_res), dev->name)) {
> + pr_err("I/O address 0x%04llx already in use device disabled\n",
> + TCOBASE);
> + ret = -EBUSY;
> goto unreg_smi_en;
> }
>
> - printk(KERN_INFO PFX
> - "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n",
> - iTCO_chipset_info[ent->driver_data].name,
> - iTCO_chipset_info[ent->driver_data].iTCO_version,
> - TCOBASE);
> + pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n",
> + ich_info->name, ich_info->iTCO_version, TCOBASE);
>
> /* Clear out the (probably old) status */
> outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */
> @@ -882,79 +596,38 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev,
> if not reset to the default */
> if (iTCO_wdt_set_heartbeat(heartbeat)) {
> iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT);
> - printk(KERN_INFO PFX
> - "timeout value out of range, using %d\n", heartbeat);
> + pr_info("timeout value out of range, using %d\n", heartbeat);
> }
>
> ret = misc_register(&iTCO_wdt_miscdev);
> if (ret != 0) {
> - printk(KERN_ERR PFX
> - "cannot register miscdev on minor=%d (err=%d)\n",
> + pr_err("cannot register miscdev on minor=%d (err=%d)\n",
> WATCHDOG_MINOR, ret);
> goto unreg_region;
> }
>
> - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
> + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
> heartbeat, nowayout);
>
> return 0;
>
> unreg_region:
> - release_region(TCOBASE, 0x20);
> + release_resource(iTCO_wdt_private.tco_res);
> unreg_smi_en:
> - release_region(SMI_EN, 4);
> + release_resource(iTCO_wdt_private.tco_res);
This doesn't look correct - you release tco_res twice. smi_res ?
> out_unmap:
> if (iTCO_wdt_private.iTCO_version == 2)
> iounmap(iTCO_wdt_private.gcs);
> out:
> - iTCO_wdt_private.ACPIBASE = 0;
> - return ret;
> -}
> -
> -static void __devexit iTCO_wdt_cleanup(void)
> -{
> - /* Stop the timer before we leave */
> - if (!nowayout)
> - iTCO_wdt_stop();
> -
> - /* Deregister */
> - misc_deregister(&iTCO_wdt_miscdev);
> - release_region(TCOBASE, 0x20);
> - release_region(SMI_EN, 4);
> - if (iTCO_wdt_private.iTCO_version == 2)
> - iounmap(iTCO_wdt_private.gcs);
> - pci_dev_put(iTCO_wdt_private.pdev);
> - iTCO_wdt_private.ACPIBASE = 0;
> -}
> -
> -static int __devinit iTCO_wdt_probe(struct platform_device *dev)
> -{
> - int ret = -ENODEV;
> - int found = 0;
> - struct pci_dev *pdev = NULL;
> - const struct pci_device_id *ent;
> -
> - spin_lock_init(&iTCO_wdt_private.io_lock);
> -
> - for_each_pci_dev(pdev) {
> - ent = pci_match_id(iTCO_wdt_pci_tbl, pdev);
> - if (ent) {
> - found++;
> - ret = iTCO_wdt_init(pdev, ent, dev);
> - if (!ret)
> - break;
> - }
> - }
> -
> - if (!found)
> - printk(KERN_INFO PFX "No device detected.\n");
> + iTCO_wdt_private.tco_res = NULL;
> + iTCO_wdt_private.smi_res = NULL;
>
> return ret;
> }
>
> static int __devexit iTCO_wdt_remove(struct platform_device *dev)
> {
> - if (iTCO_wdt_private.ACPIBASE)
> + if (iTCO_wdt_private.tco_res || iTCO_wdt_private.smi_res)
> iTCO_wdt_cleanup();
>
> return 0;
> @@ -979,32 +652,19 @@ static int __init iTCO_wdt_init_module(void)
> {
> int err;
>
> - printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s\n",
> - DRV_VERSION);
> + pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION);
>
> err = platform_driver_register(&iTCO_wdt_driver);
> if (err)
> return err;
>
> - iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME,
> - -1, NULL, 0);
> - if (IS_ERR(iTCO_wdt_platform_device)) {
> - err = PTR_ERR(iTCO_wdt_platform_device);
> - goto unreg_platform_driver;
> - }
> -
> return 0;
> -
> -unreg_platform_driver:
> - platform_driver_unregister(&iTCO_wdt_driver);
> - return err;
> }
>
> static void __exit iTCO_wdt_cleanup_module(void)
> {
> - platform_device_unregister(iTCO_wdt_platform_device);
> platform_driver_unregister(&iTCO_wdt_driver);
> - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
> + pr_info("Watchdog Module Unloaded.\n");
> }
>
> module_init(iTCO_wdt_init_module);
> diff --git a/include/linux/mfd/lpc_ich.h b/include/linux/mfd/lpc_ich.h
> index 286c778..47d64e7 100644
> --- a/include/linux/mfd/lpc_ich.h
> +++ b/include/linux/mfd/lpc_ich.h
> @@ -20,12 +20,19 @@
> #ifndef LPC_ICH_H
> #define LPC_ICH_H
>
> +/* Watchdog resources */
> +#define ICH_RES_IO_TCO 0
> +#define ICH_RES_IO_SMI 1
> +#define ICH_RES_MEM_OFF 2
> +#define ICH_RES_MEM_GCS 0
> +
> /* GPIO resources */
> #define ICH_RES_GPIO 0
> #define ICH_RES_GPE0 1
>
> struct lpc_ich_info {
> char name[32];
> + unsigned int iTCO_version;
> unsigned int gpio_version;
> };
>
> --
> 1.7.0.4
On Tue, 2012-02-07 at 16:00 -0500, Aaron Sierra wrote:
> > > +static int __devinit lpc_ich_probe(struct pci_dev *dev,
> > > + const struct pci_device_id *id)
> > > +{
> > > + u32 base_addr_cfg;
> > > + u32 base_addr;
> > > + u8 reg_save;
> > > + int ret;
> > > + int cells = 0;
> > > + int acpi_conflict = 0;
> > > +
> > You can use bool for acpi_conflict (and cells, but I don't think that
> > is needed anyway).
>
> I agree that bool is a better type choice. See my comment at the end
> regarding my reason for using cells.
>
>
> > > +pm_done:
> > > + /* Setup GPIO base register */
> > > + pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg);
> > > + base_addr = base_addr_cfg & 0x0000ff80;
> > > + if (!base_addr) {
> > > + dev_err(&dev->dev, "I/O space for GPIO
> > > uninitialized\n");
> > > + /* GPIO in power-management space may still be
> > > available */
> > > + goto gpio_reg;
> > > + }
> > > +
> > > + gpio_ich_res[ICH_RES_GPIO].start = base_addr;
> > > + gpio_ich_res[ICH_RES_GPIO].end = base_addr +
> > > GPIOBASE_IO_SIZE - 1;
> > > + ret =
> > > acpi_check_resource_conflict(&gpio_ich_res[ICH_RES_GPIO]);
> > > + if (ret) {
> > > + /* this isn't necessarily fatal for the GPIO */
> > > + gpio_ich_res[ICH_RES_GPIO].start = 0;
> > > + gpio_ich_res[ICH_RES_GPIO].end = 0;
> > > + acpi_conflict++;
> >
> > acpi_conflict = true;
> >
> > After all, it does not really matter how many conflicts were
> > detected.
>
> I agree.
>
> > > +gpio_reg:
> > > + lpc_ich_finalize_cell(&lpc_ich_cells[LPC_GPIO], id);
> > > + ret = mfd_add_devices(&dev->dev, 0,
> > > &lpc_ich_cells[LPC_GPIO],
> > > + 1, NULL, 0);
> > > + if (!ret)
> > > + cells++;
> > > +
> > So if there is an error, ret < 0, correct ?
> >
> > > + if (acpi_conflict)
> > > + dev_info(&dev->dev, "ACPI resource conflicts found;
> > > "
> > > + "consider using
> > > acpi_enforce_resources=lax?\n");
> > > +
> > > + if (cells)
> > > + return 0;
> > > + else
> > > + return -ENODEV;
> >
> > If the above is true, you can just return ret, and you don't need the
> > cells variable. Or, even better, move the acpi warning above the call
> > to mfd_add_devices().
>
> The cells variable isn't strictly necessary when we're only dealing
> with one cell registration, as we have if only looking at the lpc_ich
> and gpio-ich patches. The iTCO_wdt patch adds a second call to
> mfd_add_devices, so when we return we're interested if either of the
> calls succeeded. This was intended to be a forward thinking
> implementation, but I have no qualms about simplifying it in the
> initial lpc_ich patch.
No, just keep it - I realized that when I looked into the iTCO patch.
Thanks,
Guenter
On Tue, 07 Feb 2012 13:58:31 -0600 (CST), Aaron Sierra wrote:
> This driver works on many Intel chipsets, including the ICH6, ICH7,
> ICH8, ICH9, ICH10, 3100, Series 5/3400 (Ibex Peak), Series 6/C200
> (Cougar Point), and NM10 (Tiger Point).
>
> Additional Intel chipsets should be easily supported if needed, eg the
> ICH1-5, EP80579, etc.
>
> Tested on QM67 (Cougar Point), QM57 (Ibex Peak), 3100 (Whitmore Lake),
> and NM10 (Tiger Point).
> (...)
> +static int ichx_write_bit(int reg, unsigned nr, int val, int verify)
> +{
> + unsigned long flags;
> + u32 data, tmp;
> + int reg_nr = nr / 32;
> + int bit = nr & 0x1f;
> + int ret = 0;
> +
> + spin_lock_irqsave(&ichx_priv.lock, flags);
> +
> + data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + data = (data & ~(1 << bit)) | (val << bit);
I posted a fix for this function last Sunday:
Subject: [PATCH] gpio-ich: Fix setting GPIO value
Date: Sun, 5 Feb 2012 21:44:44 +0100
Please apply it, otherwise your driver doesn't implement the gpio
driver API properly and code running on top of it (consumer drivers)
may fail.
> + ICHX_WRITE(data, ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + tmp = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> + if (verify && (data != tmp))
> + ret = -EPERM;
> +
> + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> +
> + return ret;
> +}
--
Jean Delvare
> > +static int ichx_write_bit(int reg, unsigned nr, int val, int
> > verify)
> > +{
> > + unsigned long flags;
> > + u32 data, tmp;
> > + int reg_nr = nr / 32;
> > + int bit = nr & 0x1f;
> > + int ret = 0;
> > +
> > + spin_lock_irqsave(&ichx_priv.lock, flags);
> > +
> > + data = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> > + data = (data & ~(1 << bit)) | (val << bit);
>
> I posted a fix for this function last Sunday:
>
> Subject: [PATCH] gpio-ich: Fix setting GPIO value
> Date: Sun, 5 Feb 2012 21:44:44 +0100
>
> Please apply it, otherwise your driver doesn't implement the gpio
> driver API properly and code running on top of it (consumer drivers)
> may fail.
>
Jean,
Thanks for the reminder and for the patch. I have applied it to my tree.
-Aaron
> > + ICHX_WRITE(data, ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> > + tmp = ICHX_READ(ichx_regs[reg][reg_nr], ichx_priv.gpio_base);
> > + if (verify && (data != tmp))
> > + ret = -EPERM;
> > +
> > + spin_unlock_irqrestore(&ichx_priv.lock, flags);
> > +
> > + return ret;
> > +}
>
> --
> Jean Delvare
>
> > @@ -429,11 +463,52 @@ static int __devinit lpc_ich_probe(struct
> > pci_dev *dev,
> > acpi_conflict++;
> > }
> >
> > + wdt_io_res(ICH_RES_IO_TCO)->start = base_addr +
> > ACPIBASE_TCO_OFF;
> > + wdt_io_res(ICH_RES_IO_TCO)->end = base_addr +
> > ACPIBASE_TCO_END;
> > + ret =
> > acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_TCO));
> > + if (ret) {
> > + acpi_conflict++;
> > + goto pm_done;
> > + }
> > +
> > + wdt_io_res(ICH_RES_IO_SMI)->start = base_addr +
> > ACPIBASE_SMI_OFF;
> > + wdt_io_res(ICH_RES_IO_SMI)->end = base_addr +
> > ACPIBASE_SMI_END;
> > + ret =
> > acpi_check_resource_conflict(wdt_io_res(ICH_RES_IO_SMI));
> > + if (ret) {
> > + acpi_conflict++;
> > + goto pm_done;
> > + }
> > +
> I'll have to look into the merged code, but doesn't this mean that
> iTCO resource requirement conflicts impact GPIO resource
> requirements ? If yes, is it possible to keep those separate ?
I believe that I accounted for this properly. I attempt to assign
the GPE0 resource before the iTCO resources. That way an ACPI
conflict with the iTCO resources can't cause the GPE0 resource to not
be assigned.
> > +static int __devinit iTCO_wdt_probe(struct platform_device *dev)
> > {
> > int ret;
> > - u32 base_address;
> > - unsigned long RCBA;
> > unsigned long val32;
> > + struct lpc_ich_info *ich_info = dev->dev.platform_data;
> >
> > - /*
> > - * Find the ACPI/PM base I/O address which is the base
> > - * for the TCO registers (TCOBASE=ACPIBASE + 0x60)
> > - * ACPIBASE is bits [15:7] from 0x40-0x43
> > - */
> > - pci_read_config_dword(pdev, 0x40, &base_address);
> > - base_address &= 0x0000ff80;
> > - if (base_address == 0x00000000) {
> > - /* Something's wrong here, ACPIBASE has to be set
> > */
> > - printk(KERN_ERR PFX "failed to get TCOBASE address,
> > "
> > - "device disabled by
> > hardware/BIOS\n");
> > + if (!ich_info)
> > + return -ENODEV;
> > +
> > + spin_lock_init(&iTCO_wdt_private.io_lock);
> > +
> > + iTCO_wdt_private.tco_res =
> > + platform_get_resource(dev, IORESOURCE_IO,
> > ICH_RES_IO_TCO);
> > +
> > + iTCO_wdt_private.smi_res =
> > + platform_get_resource(dev, IORESOURCE_IO,
> > ICH_RES_IO_SMI);
> > +
> > + iTCO_wdt_private.gcs_res =
> > + platform_get_resource(dev, IORESOURCE_MEM,
> > ICH_RES_MEM_GCS);
> > +
> > + if (!iTCO_wdt_private.tco_res || !iTCO_wdt_private.smi_res
> > ||
> > + !iTCO_wdt_private.gcs_res) {
> > + pr_info("No device detected.\n");
> > return -ENODEV;
This test and return doesn't account for any of the resources that may
have been successfully obtained.
> > }
> > - iTCO_wdt_private.iTCO_version =
> > -
> > iTCO_chipset_info[ent->driver_data].iTCO_version;
> > - iTCO_wdt_private.ACPIBASE = base_address;
> > - iTCO_wdt_private.pdev = pdev;
> > -
> > - /* Get the Memory-Mapped GCS register, we need it for the
> > - NO_REBOOT flag (TCO v2). To get access to it you have to
> > - read RCBA from PCI Config space 0xf0 and use it as base.
> > - GCS = RCBA + ICH6_GCS(0x3410). */
> > +
> > + iTCO_wdt_private.iTCO_version = ich_info->iTCO_version;
> > + iTCO_wdt_private.dev = dev;
> > + iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent);
> > +
> > + /*
> > + * Get the Memory-Mapped GCS register, we need it for the
> > + * NO_REBOOT flag (TCO v2).
> > + */
> > if (iTCO_wdt_private.iTCO_version == 2) {
> > - pci_read_config_dword(pdev, 0xf0, &base_address);
> > - if ((base_address & 1) == 0) {
> > - printk(KERN_ERR PFX "RCBA is disabled by
> > hardware"
> > - "/BIOS, device
> > disabled\n");
> > - ret = -ENODEV;
> > + if
> > (!request_mem_region(iTCO_wdt_private.gcs_res->start,
> > + resource_size(iTCO_wdt_private.gcs_res),
> > dev->name)) {
> > + ret = -EBUSY;
> > goto out;
> > }
> > - RCBA = base_address & 0xffffc000;
> > - iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4);
> > + iTCO_wdt_private.gcs =
> > ioremap(iTCO_wdt_private.gcs_res->start,
> > + resource_size(iTCO_wdt_private.gcs_res));
> > }
> >
> > /* Check chipset's NO_REBOOT bit */
> > if (iTCO_wdt_unset_NO_REBOOT_bit() &&
> > iTCO_vendor_check_noreboot_on()) {
> > - printk(KERN_INFO PFX "unable to reset NO_REBOOT
> > flag, "
> > + pr_info("unable to reset NO_REBOOT flag, "
> > "device disabled by
> > hardware/BIOS\n");
> > ret = -ENODEV; /* Cannot reset NO_REBOOT bit */
> > goto out_unmap;
> > @@ -841,11 +559,11 @@ static int __devinit iTCO_wdt_init(struct
> > pci_dev *pdev,
> > iTCO_wdt_set_NO_REBOOT_bit();
> >
> > /* The TCO logic uses the TCO_EN bit in the SMI_EN register
> > */
> > - if (!request_region(SMI_EN, 4, "iTCO_wdt")) {
> > - printk(KERN_ERR PFX
> > - "I/O address 0x%04lx already in use, "
> > + if (!request_region(iTCO_wdt_private.smi_res->start,
> > + resource_size(iTCO_wdt_private.smi_res),
> > dev->name)) {
> > + pr_err("I/O address 0x%04llx already in use, "
> > "device
> > disabled\n",
> > SMI_EN);
> > - ret = -EIO;
> > + ret = -EBUSY;
> > goto out_unmap;
> > }
> > if (turn_SMI_watchdog_clear_off >=
> > iTCO_wdt_private.iTCO_version) {
> > @@ -855,20 +573,16 @@ static int __devinit iTCO_wdt_init(struct
> > pci_dev *pdev,
> > outl(val32, SMI_EN);
> > }
> >
> > - /* The TCO I/O registers reside in a 32-byte range pointed
> > to
> > - by the TCOBASE value */
> > - if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) {
> > - printk(KERN_ERR PFX "I/O address 0x%04lx already in
> > use "
> > - "device
> > disabled\n", TCOBASE);
> > - ret = -EIO;
> > + if (!request_region(iTCO_wdt_private.tco_res->start,
> > + resource_size(iTCO_wdt_private.tco_res),
> > dev->name)) {
> > + pr_err("I/O address 0x%04llx already in use device
> > disabled\n",
> > + TCOBASE);
> > + ret = -EBUSY;
> > goto unreg_smi_en;
> > }
> >
> > - printk(KERN_INFO PFX
> > - "Found a %s TCO device (Version=%d,
> > TCOBASE=0x%04lx)\n",
> > - iTCO_chipset_info[ent->driver_data].name,
> > -
> > iTCO_chipset_info[ent->driver_data].iTCO_version,
> > - TCOBASE);
> > + pr_info("Found a %s TCO device (Version=%d,
> > TCOBASE=0x%04llx)\n",
> > + ich_info->name, ich_info->iTCO_version, TCOBASE);
> >
> > /* Clear out the (probably old) status */
> > outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */
> > @@ -882,79 +596,38 @@ static int __devinit iTCO_wdt_init(struct
> > pci_dev *pdev,
> > if not reset to the default */
> > if (iTCO_wdt_set_heartbeat(heartbeat)) {
> > iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT);
> > - printk(KERN_INFO PFX
> > - "timeout value out of range, using %d\n",
> > heartbeat);
> > + pr_info("timeout value out of range, using %d\n",
> > heartbeat);
> > }
> >
> > ret = misc_register(&iTCO_wdt_miscdev);
> > if (ret != 0) {
> > - printk(KERN_ERR PFX
> > - "cannot register miscdev on minor=%d
> > (err=%d)\n",
> > + pr_err("cannot register miscdev on minor=%d
> > (err=%d)\n",
> > WATCHDOG_MINOR,
> > ret);
> > goto unreg_region;
> > }
> >
> > - printk(KERN_INFO PFX "initialized. heartbeat=%d sec
> > (nowayout=%d)\n",
> > + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
> > heartbeat,
> > nowayout);
> >
> > return 0;
> >
> > unreg_region:
> > - release_region(TCOBASE, 0x20);
> > + release_resource(iTCO_wdt_private.tco_res);
> > unreg_smi_en:
> > - release_region(SMI_EN, 4);
> > + release_resource(iTCO_wdt_private.tco_res);
>
> This doesn't look correct - you release tco_res twice. smi_res ?
Also, a release for gcs_res is currently missing here...
>
> > out_unmap:
> > if (iTCO_wdt_private.iTCO_version == 2)
> > iounmap(iTCO_wdt_private.gcs);
> > out:
> > - iTCO_wdt_private.ACPIBASE = 0;
> > - return ret;
> > -}
> > -