Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Unified Diff: drivers/gpio/nm10_gpio.c

Issue 2231003: NM10 gpio driver implementation (Closed) Base URL: ssh://git@chromiumos-git/kernel.git
Patch Set: Minor changes to address review comments. Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « drivers/gpio/Makefile ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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");
« no previous file with comments | « drivers/gpio/Makefile ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698