| Index: drivers/gpio/nm10_gpio.c
|
| diff --git a/drivers/gpio/nm10_gpio.c b/drivers/gpio/nm10_gpio.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2802f55a286eb3afd3cee9e1419303fe64ab528f
|
| --- /dev/null
|
| +++ b/drivers/gpio/nm10_gpio.c
|
| @@ -0,0 +1,299 @@
|
| +/*
|
| + * Copyright (c) 2010, The Chromium OS Authors
|
| + *
|
| + * This program is free software; you can redistribute it and/or modify
|
| + * it under the terms of the GNU General Public License version 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; if not, write to the Free Software
|
| + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
| + *
|
| + * This driver supports GPIO controller of the NM10 chip.
|
| + *
|
| + * The NM10 has many GPIO pins, the exact number depends on the configuration,
|
| + * as some of the pins can be used for other than GPIO purposes. The GPIO
|
| + * controller has provision of managing of up to 64 bits. Each bit can be
|
| + * configured as 'not available', (when for other than GPIO purposes), or a
|
| + * GPIO input/output.
|
| + *
|
| + * Even though NM10 provides the ability to change GPIO bits' directions, this
|
| + * driver does NOT allow to change the use of the bits. Whatever the system is
|
| + * strapped and/or configured by BIOS for is used by the driver.
|
| + *
|
| + * This driver plugs in into the existing linux gpio infrastructure and allows
|
| + * to instantiate all 64 bits (through writing into /sys/class/gpio/export)
|
| + * even though not all bits can be used. This simplifies bit mapping between
|
| + * hardware and software.
|
| + *
|
| + * Attempts to write into unsupported bits are silently ignored. Attempts to
|
| + * read unsupported bits return value of zero.
|
| + *
|
| + * For the lower 32 GPIO bits the NM10 provides the ability to 'blink'
|
| + * (alternate 1 and zero with 1Hz frequency at approximately 50% duty cycle)
|
| + * on output and invert level on input. This driver does not provide access
|
| + * these features.
|
| + *
|
| + * The NM10 GPIO controller is a part of the LPC PCI device, (PCI device ID
|
| + * 0x27bc). The GPIO register file is mapped to the IO space. An earlier Intel
|
| + * chip, the ICH7M south bridge, has a similar GPIO controller, it could be
|
| + * also supported by this driver.
|
| + *
|
| + * "Intel NM10 Family Express Chipset" datasheet of Dec 2009 (document number
|
| + * 322896-001) was used as a reference when writing this driver.
|
| + *
|
| + */
|
| +
|
| +#include <linux/module.h>
|
| +#include <linux/pci.h>
|
| +#include <linux/gpio.h>
|
| +
|
| +static char gpio_driver_name[] = "nm10_gpio";
|
| +static char gpio_driver_version[] = "0.04";
|
| +
|
| +/* NM10 GPIO register file definitions, offsets in the IO space */
|
| +#define NM10_GPIO_USE_SEL 0
|
| +#define NM10_GPIO_IO_SEL 4
|
| +#define NM10_GPIO_LVL 0xc
|
| +#define NM10_GPIO_USE_SEL2 0x30
|
| +#define NM10_GPIO_IO_SEL2 0x34
|
| +#define NM10_GPIO_LVL2 0x38
|
| +#define NM10_GPIO_REG_FILE_SIZE 0x40
|
| +
|
| +/* Structure describing one GPIO section in the nm10, accessing 32 GPIO bits. */
|
| +struct nm10_gpio_info {
|
| + u_char use_select_offset;
|
| + u_char io_select_offset;
|
| + u_char io_level_offset;
|
| +};
|
| +
|
| +/* This array describes two NM10 GPIO sections */
|
| +const struct nm10_gpio_info nm10_gpio_sections[] = {
|
| + {NM10_GPIO_USE_SEL, NM10_GPIO_IO_SEL, NM10_GPIO_LVL},
|
| + {NM10_GPIO_USE_SEL2, NM10_GPIO_IO_SEL2, NM10_GPIO_LVL2},
|
| +};
|
| +
|
| +#define NM10_GPIO_BITS_PER_SECTION 32
|
| +#define NM10_GPIO_SECTIONS ARRAY_SIZE(nm10_gpio_sections)
|
| +#define NM10_MAX_GPIO_BITS (NM10_GPIO_SECTIONS * NM10_GPIO_BITS_PER_SECTION)
|
| +
|
| +/*
|
| + * Structure representing a single NM10 GPIO driver instance.
|
| + */
|
| +struct nm10_gpio {
|
| + struct gpio_chip chip;
|
| + u32 io_base; /* base IO space address of the GPIO register file */
|
| +
|
| + /* cached contents of the GPIO bit selections, read during driver
|
| + * installation */
|
| + u32 cached_select[NM10_GPIO_SECTIONS];
|
| +};
|
| +
|
| +/**
|
| + * nm10_gpio_get() - get value of a GPIO bit
|
| + * @chip: generic gpio chip handle associated with this module
|
| + * @offset: zero based GPIO bit number (in this controller's scope).
|
| + *
|
| + * Returns zero in cases when offset exceeds the chip's GPIO capacity, or the
|
| + * passed in bit not used for GPIO. If the offset is of a valid bit - returns
|
| + * a bitmask with the bit value matching the actual bit input state.
|
| + */
|
| +static int nm10_gpio_get(struct gpio_chip *chip, unsigned offset)
|
| +{
|
| + u8 section;
|
| + u32 bit;
|
| + struct nm10_gpio *pgpio = container_of(chip, struct nm10_gpio, chip);
|
| +
|
| + section = offset / NM10_GPIO_BITS_PER_SECTION;
|
| + bit = BIT(offset % NM10_GPIO_BITS_PER_SECTION);
|
| +
|
| + if (section >= NM10_GPIO_SECTIONS) {
|
| + printk(KERN_ERR "%s: bad offset %d\n",
|
| + gpio_driver_name, offset);
|
| + return 0;
|
| + }
|
| +
|
| + if (!(bit & pgpio->cached_select[section])) {
|
| + return 0; /* this bit is not used for GPIO */
|
| + }
|
| + return inl(pgpio->io_base +
|
| + nm10_gpio_sections[section].io_level_offset) & bit;
|
| +}
|
| +
|
| +/**
|
| + * nm10_gpio_set() - get value of a GPIO bit
|
| + * @chip: generic gpio chip handle associated with this module
|
| + * @offset: zero based GPIO bit number (in this controller's scope).
|
| + *
|
| + * If the offset is of a valid bit (used for GPIO output) - the bit state is
|
| + * changed to reflect the value. All other in range offset values are ignored.
|
| + */
|
| +static void nm10_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
| +{
|
| + u8 section;
|
| + u32 bit;
|
| + const struct nm10_gpio_info *pinfo;
|
| + struct nm10_gpio *pgpio = container_of(chip, struct nm10_gpio, chip);
|
| + u32 gpio_reg_value;
|
| +
|
| + section = offset / NM10_GPIO_BITS_PER_SECTION;
|
| + bit = BIT(offset % NM10_GPIO_BITS_PER_SECTION);
|
| +
|
| + if (section >= NM10_GPIO_SECTIONS) {
|
| + printk(KERN_ERR "%s: bad offset %d\n",
|
| + gpio_driver_name, offset);
|
| + }
|
| +
|
| + if (!(bit & pgpio->cached_select[section])) {
|
| + return; /* this bit is not used for GPIO */
|
| + }
|
| +
|
| + pinfo = nm10_gpio_sections + section;
|
| + if (inl(pgpio->io_base + pinfo->io_select_offset) & bit) {
|
| + return; /* this is an input bit */
|
| + }
|
| +
|
| + gpio_reg_value = inl(pgpio->io_base + pinfo->io_level_offset);
|
| +
|
| + if (value) {
|
| + gpio_reg_value |= bit;
|
| + } else {
|
| + gpio_reg_value &= ~bit;
|
| + }
|
| + outl(gpio_reg_value, pgpio->io_base + pinfo->io_level_offset);
|
| +}
|
| +
|
| +static int __devinit nm10_gpio_probe(struct pci_dev *pdev,
|
| + const struct pci_device_id *id)
|
| +{
|
| + int retval, ii;
|
| + u32 value;
|
| + struct nm10_gpio *pgpio;
|
| +
|
| + retval = pci_enable_device(pdev);
|
| + printk(KERN_INFO "%s version %s built on %s at %s\n", gpio_driver_name,
|
| + gpio_driver_version, __DATE__, __TIME__);
|
| +
|
| + if (retval) {
|
| + goto done;
|
| + }
|
| +
|
| + /* actual IO space offset of the GPIO block */
|
| + retval = pci_read_config_dword(pdev, 0x48, &value);
|
| + if (retval || !(value & 1)) {
|
| + dev_err(&pdev->dev,
|
| + "failed retrieving IO base addr, got %d(0x%x)\n",
|
| + retval, value);
|
| + goto err2;
|
| + }
|
| +
|
| + value &= ~1; /* clear the IO space flag */
|
| + if (!request_region(value, NM10_GPIO_REG_FILE_SIZE, gpio_driver_name)) {
|
| + dev_err(&pdev->dev, "error requesting REGION\n");
|
| + retval = -ENOMEM;
|
| + goto err2;
|
| + }
|
| +
|
| + pgpio = kzalloc(sizeof(struct nm10_gpio), GFP_KERNEL);
|
| + if (!pgpio) {
|
| + dev_err(&pdev->dev, "can't allocate nm10_gpio structure\n");
|
| + retval = -ENOMEM;
|
| + goto err3;
|
| + }
|
| +
|
| + /* used to access GPIO bits on this chip */
|
| + pgpio->io_base = value;
|
| +
|
| + pgpio->chip.base = -1;
|
| + pgpio->chip.label = dev_name(&pdev->dev);
|
| + pgpio->chip.get = nm10_gpio_get;
|
| + pgpio->chip.set = nm10_gpio_set;
|
| + pgpio->chip.ngpio = NM10_MAX_GPIO_BITS;
|
| + pgpio->chip.can_sleep = 0;
|
| + pci_set_drvdata(pdev, pgpio);
|
| +
|
| + /* store GPIO configuration locally */
|
| + for (ii = 0; ii < ARRAY_SIZE(pgpio->cached_select); ii++) {
|
| + pgpio->cached_select[ii] = inl(pgpio->io_base +
|
| + nm10_gpio_sections[ii].
|
| + use_select_offset);
|
| + }
|
| +
|
| + retval = gpiochip_add(&pgpio->chip);
|
| + if (!retval) {
|
| + goto done;
|
| + }
|
| +
|
| + dev_err(&pdev->dev, "%s gpiochip_add error %d\n",
|
| + gpio_driver_name, retval);
|
| +
|
| + kfree(pgpio);
|
| + err3:
|
| + release_region(value, NM10_GPIO_REG_FILE_SIZE);
|
| + err2:
|
| + pci_disable_device(pdev);
|
| + done:
|
| + if (retval) {
|
| + printk(KERN_ERR "%s failed!\n", gpio_driver_name);
|
| +
|
| + }
|
| + return retval;
|
| +}
|
| +
|
| +/**
|
| + * nm10_gpio_remove() - remove the NM10 gpio driver module.
|
| + * @pdev: pci device associated with this driver module
|
| + *
|
| + */
|
| +static void nm10_gpio_remove(struct pci_dev *pdev)
|
| +{
|
| + struct nm10_gpio *pgpio = pci_get_drvdata(pdev);
|
| + int base = pgpio->chip.base;
|
| +
|
| + if (gpiochip_remove(&pgpio->chip)) {
|
| + printk(KERN_ERR "%s: failed removing!\n", gpio_driver_name);
|
| + return;
|
| + }
|
| +
|
| + release_region(pgpio->io_base, NM10_GPIO_REG_FILE_SIZE);
|
| + pci_disable_device(pdev);
|
| + pci_set_drvdata(pdev, NULL);
|
| + kfree(pgpio);
|
| + printk(KERN_INFO "%s base %d removed\n", gpio_driver_name, base);
|
| +}
|
| +
|
| +static struct pci_device_id nm10_gpio_ids[] = {
|
| + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_TGP_LPC)},
|
| + {0,}
|
| +};
|
| +
|
| +MODULE_DEVICE_TABLE(pci, nm10_gpio_ids);
|
| +
|
| +static struct pci_driver nm10_gpio_pci_driver = {
|
| + .name = gpio_driver_name,
|
| + .id_table = nm10_gpio_ids,
|
| + .probe = nm10_gpio_probe,
|
| + .remove = nm10_gpio_remove
|
| +};
|
| +
|
| +static int __init nm10_gpio_init(void)
|
| +{
|
| + return pci_register_driver(&nm10_gpio_pci_driver);
|
| +}
|
| +
|
| +static void __exit nm10_gpio_exit(void)
|
| +{
|
| + pci_unregister_driver(&nm10_gpio_pci_driver);
|
| +}
|
| +
|
| +module_init(nm10_gpio_init);
|
| +module_exit(nm10_gpio_exit);
|
| +
|
| +MODULE_LICENSE("GPL");
|
| +MODULE_AUTHOR("The Chromium OS Authors");
|
| +MODULE_DESCRIPTION("NM10 GPIO driver");
|
|
|