gpio: gpio-ich: Share ownership of GPIO groups
authorJean Delvare <khali@linux-fr.org>
Mon, 23 Jul 2012 15:34:15 +0000 (17:34 +0200)
committerSamuel Ortiz <sameo@linux.intel.com>
Fri, 14 Sep 2012 07:52:13 +0000 (09:52 +0200)
The ICH chips have their GPIO pins organized in 2 or 3 independent
groups of 32 GPIO pins. It can happen that the ACPI BIOS wants to make
use of pins in one group, preventing the OS to access these. This does
not prevent the OS from accessing the other group(s).

This is the case for example on my Asus Z8NA-D6 board. The ACPI BIOS
wants to control GPIO 18 (group 1), while I (the OS) need to control
GPIO 52 and 53 (group 2) for SMBus multiplexing.

So instead of checking for ACPI resource conflict on the whole I/O
range, check on a per-group basis, and consider it a success if at
least one of the groups is available for the OS to use.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Cc: Peter Tyser <ptyser@xes-inc.com>
Cc: Aaron Sierra <asierra@xes-inc.com>
Cc: Grant Likely <grant.likely@secretlab.ca>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
drivers/gpio/gpio-ich.c
drivers/mfd/lpc_ich.c
include/linux/mfd/lpc_ich.h

index b7c06517403dab8acc257570f6bdb83d6036aa94..d4d61796669675696ce30711796773b4f7d1fa36 100644 (file)
@@ -49,6 +49,10 @@ static const u8 ichx_regs[3][3] = {
        {0x0c, 0x38, 0x48},     /* LVL[1-3] offsets */
 };
 
+static const u8 ichx_reglen[3] = {
+       0x30, 0x10, 0x10,
+};
+
 #define ICHX_WRITE(val, reg, base_res) outl(val, (reg) + (base_res)->start)
 #define ICHX_READ(reg, base_res)       inl((reg) + (base_res)->start)
 
@@ -75,6 +79,7 @@ static struct {
        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 */
+       u8 use_gpio;            /* Which GPIO groups are usable */
 } ichx_priv;
 
 static int modparam_gpiobase = -1;     /* dynamic */
@@ -123,8 +128,16 @@ static int ichx_read_bit(int reg, unsigned nr)
        return data & (1 << bit) ? 1 : 0;
 }
 
+static int ichx_gpio_check_available(struct gpio_chip *gpio, unsigned nr)
+{
+       return (ichx_priv.use_gpio & (1 << (nr / 32))) ? 0 : -ENXIO;
+}
+
 static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
 {
+       if (!ichx_gpio_check_available(gpio, nr))
+               return -ENXIO;
+
        /*
         * Try setting pin as an input and verify it worked since many pins
         * are output-only.
@@ -138,6 +151,9 @@ static int ichx_gpio_direction_input(struct gpio_chip *gpio, unsigned nr)
 static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
                                        int val)
 {
+       if (!ichx_gpio_check_available(gpio, nr))
+               return -ENXIO;
+
        /* Set GPIO output value. */
        ichx_write_bit(GPIO_LVL, nr, val, 0);
 
@@ -153,6 +169,9 @@ static int ichx_gpio_direction_output(struct gpio_chip *gpio, unsigned nr,
 
 static int ichx_gpio_get(struct gpio_chip *chip, unsigned nr)
 {
+       if (!ichx_gpio_check_available(chip, nr))
+               return -ENXIO;
+
        return ichx_read_bit(GPIO_LVL, nr);
 }
 
@@ -161,6 +180,9 @@ static int ich6_gpio_get(struct gpio_chip *chip, unsigned nr)
        unsigned long flags;
        u32 data;
 
+       if (!ichx_gpio_check_available(chip, nr))
+               return -ENXIO;
+
        /*
         * GPI 0 - 15 need to be read from the power management registers on
         * a ICH6/3100 bridge.
@@ -291,6 +313,46 @@ static struct ichx_desc intel5_desc = {
        .ngpio = 76,
 };
 
+static int __devinit ichx_gpio_request_regions(struct resource *res_base,
+                                               const char *name, u8 use_gpio)
+{
+       int i;
+
+       if (!res_base || !res_base->start || !res_base->end)
+               return -ENODEV;
+
+       for (i = 0; i < ARRAY_SIZE(ichx_regs[0]); i++) {
+               if (!(use_gpio & (1 << i)))
+                       continue;
+               if (!request_region(res_base->start + ichx_regs[0][i],
+                                   ichx_reglen[i], name))
+                       goto request_err;
+       }
+       return 0;
+
+request_err:
+       /* Clean up: release already requested regions, if any */
+       for (i--; i >= 0; i--) {
+               if (!(use_gpio & (1 << i)))
+                       continue;
+               release_region(res_base->start + ichx_regs[0][i],
+                              ichx_reglen[i]);
+       }
+       return -EBUSY;
+}
+
+static void ichx_gpio_release_regions(struct resource *res_base, u8 use_gpio)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(ichx_regs[0]); i++) {
+               if (!(use_gpio & (1 << i)))
+                       continue;
+               release_region(res_base->start + ichx_regs[0][i],
+                              ichx_reglen[i]);
+       }
+}
+
 static int __devinit ichx_gpio_probe(struct platform_device *pdev)
 {
        struct resource *res_base, *res_pm;
@@ -329,12 +391,11 @@ static int __devinit ichx_gpio_probe(struct platform_device *pdev)
        }
 
        res_base = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_GPIO);
-       if (!res_base || !res_base->start || !res_base->end)
-               return -ENODEV;
-
-       if (!request_region(res_base->start, resource_size(res_base),
-                               pdev->name))
-               return -EBUSY;
+       ichx_priv.use_gpio = ich_info->use_gpio;
+       err = ichx_gpio_request_regions(res_base, pdev->name,
+                                       ichx_priv.use_gpio);
+       if (err)
+               return err;
 
        ichx_priv.gpio_base = res_base;
 
@@ -374,8 +435,7 @@ init:
        return 0;
 
 add_err:
-       release_region(ichx_priv.gpio_base->start,
-                       resource_size(ichx_priv.gpio_base));
+       ichx_gpio_release_regions(ichx_priv.gpio_base, ichx_priv.use_gpio);
        if (ichx_priv.pm_base)
                release_region(ichx_priv.pm_base->start,
                                resource_size(ichx_priv.pm_base));
@@ -393,8 +453,7 @@ static int __devexit ichx_gpio_remove(struct platform_device *pdev)
                return err;
        }
 
-       release_region(ichx_priv.gpio_base->start,
-                               resource_size(ichx_priv.gpio_base));
+       ichx_gpio_release_regions(ichx_priv.gpio_base, ichx_priv.use_gpio);
        if (ichx_priv.pm_base)
                release_region(ichx_priv.pm_base->start,
                                resource_size(ichx_priv.pm_base));
index 092ad4b44b6d67b9b4ee039fccc73da1748d3a2c..d142622a3fb017d2d86155fd615ed71631f4f179 100644 (file)
@@ -683,6 +683,30 @@ static void __devinit lpc_ich_finalize_cell(struct mfd_cell *cell,
        cell->pdata_size = sizeof(struct lpc_ich_info);
 }
 
+/*
+ * We don't check for resource conflict globally. There are 2 or 3 independent
+ * GPIO groups and it's enough to have access to one of these to instantiate
+ * the device.
+ */
+static int __devinit lpc_ich_check_conflict_gpio(struct resource *res)
+{
+       int ret;
+       u8 use_gpio = 0;
+
+       if (resource_size(res) >= 0x50 &&
+           !acpi_check_region(res->start + 0x40, 0x10, "LPC ICH GPIO3"))
+               use_gpio |= 1 << 2;
+
+       if (!acpi_check_region(res->start + 0x30, 0x10, "LPC ICH GPIO2"))
+               use_gpio |= 1 << 1;
+
+       ret = acpi_check_region(res->start + 0x00, 0x30, "LPC ICH GPIO1");
+       if (!ret)
+               use_gpio |= 1 << 0;
+
+       return use_gpio ? use_gpio : ret;
+}
+
 static int __devinit lpc_ich_init_gpio(struct pci_dev *dev,
                                const struct pci_device_id *id)
 {
@@ -740,12 +764,13 @@ gpe0_done:
                break;
        }
 
-       ret = acpi_check_resource_conflict(res);
-       if (ret) {
+       ret = lpc_ich_check_conflict_gpio(res);
+       if (ret < 0) {
                /* this isn't necessarily fatal for the GPIO */
                acpi_conflict = true;
                goto gpio_done;
        }
+       lpc_chipset_info[id->driver_data].use_gpio = ret;
        lpc_ich_enable_gpio_space(dev);
 
        lpc_ich_finalize_cell(&lpc_ich_cells[LPC_GPIO], id);
index fec5256c3f5d47a68239c60a051c1225640a6b20..3e1df644c407ed6d6965dc62908cb620421ca70a 100644 (file)
@@ -43,6 +43,7 @@ struct lpc_ich_info {
        char name[32];
        unsigned int iTCO_version;
        unsigned int gpio_version;
+       u8 use_gpio;
 };
 
 #endif