/* * 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. * * This driver also supports the Intel 6 Series chipset. This chipset * supports 96 GPIOs instead of 64 that NM10 does. Datasheet here: * http://www.intel.com/content/www/us/en/chipsets/6-chipset-c200-chipset-datasheet.html * */ #include #include #include #include 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_USE_SEL3 0x40 #define NM10_GPIO_IO_SEL3 0x44 #define NM10_GPIO_LVL3 0x48 #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}, {NM10_GPIO_USE_SEL3, NM10_GPIO_IO_SEL3, NM10_GPIO_LVL3}, }; #define NM10_GPIO_BITS_PER_SECTION 32 #define NM10_GPIO_SECTIONS ARRAY_SIZE(nm10_gpio_sections) static u32 max_gpio_bits; #define NM10_MAX_GPIO_BITS max_gpio_bits /* * 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_get_parameters() - get value of a GPIO bit * * Inputs: * @chip: generic gpio chip handle associated with this module * @offset: zero based GPIO bit number (in this controller's scope). * * Outputs: * @psection - pointer to the NM10 section number containing bit offset * @pbit,- pointer to the offset's bit mask within the section * @pgpio - pointer to address of this nm10_gpio instance. * * Returns zero on errors or nonzero on success. */ static int nm10_get_parameters(struct gpio_chip *chip, unsigned offset, u8* psection, u32* pbit, struct nm10_gpio **pgpio) { *pgpio = container_of(chip, struct nm10_gpio, chip); *psection = offset / NM10_GPIO_BITS_PER_SECTION; *pbit = BIT(offset % NM10_GPIO_BITS_PER_SECTION); if (*psection >= NM10_GPIO_SECTIONS) { printk(KERN_ERR "%s: bad offset %d\n", gpio_driver_name, offset); return 0; } if (!(*pbit & (*pgpio)->cached_select[*psection])) { return 0; /* this bit is not used for GPIO */ } return ~0; } /** * 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); if (!nm10_get_parameters(chip, offset, §ion, &bit, &pgpio)) { return 0; } return inl(pgpio->io_base + nm10_gpio_sections[section].io_level_offset) & bit; } /** * nm10_gpio_set() - set 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). * @value: the value to set the output to * * 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; u32 gpio_reg_value; if (!nm10_get_parameters(chip, offset, §ion, &bit, &pgpio)) { return; } 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); } /** * nm10_gpio_direction_inp() configure signal "offset" as input, or return error * @chip: generic gpio chip handle associated with this module * @offset: zero based GPIO bit number (in this controller's scope). */ static int nm10_gpio_direction_inp(struct gpio_chip *chip, unsigned offset) { u8 section; u32 bit; struct nm10_gpio *pgpio = container_of(chip, struct nm10_gpio, chip); u32 io_select_offset; if (!nm10_get_parameters(chip, offset, §ion, &bit, &pgpio)) { return -1; } io_select_offset = pgpio->io_base + nm10_gpio_sections[section].io_select_offset; outl(inl(io_select_offset) | bit, io_select_offset); return 0; } /** * nm10_gpio_direction_out() configure signal "offset" as output, * or return error * @chip: generic gpio chip handle associated with this module * @offset: zero based GPIO bit number (in this controller's scope). * @value: the value to set the output to */ static int nm10_gpio_direction_out(struct gpio_chip *chip, unsigned offset, int value) { u8 section; u32 bit; struct nm10_gpio *pgpio = container_of(chip, struct nm10_gpio, chip); u32 io_select_offset; if (!nm10_get_parameters(chip, offset, §ion, &bit, &pgpio)) { return -1; } io_select_offset = pgpio->io_base + nm10_gpio_sections[section].io_select_offset; outl(inl(io_select_offset) & ~bit, io_select_offset); nm10_gpio_set(chip, offset, value); return 0; } static int 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; } if (id->device == PCI_DEVICE_ID_INTEL_TGP_LPC) /* NM10 supports 64 GPIOs */ max_gpio_bits = 64; else /* Cougarpoint supports 96 GPIOs */ max_gpio_bits = 96; /* 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.direction_input = nm10_gpio_direction_inp; pgpio->chip.direction_output = nm10_gpio_direction_out; 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; release_region(pgpio->io_base, NM10_GPIO_REG_FILE_SIZE); if (gpiochip_remove(&pgpio->chip)) { printk(KERN_ERR "%s: failed removing!\n", gpio_driver_name); return; } 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)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_Z68)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_P67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_UM67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_HM65)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_H67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_HM67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_Q65)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_QS67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_Q67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_QM67)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_B65)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_C202)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_C204)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_C206)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_LPC_H61)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_B75)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_C216)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_H77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_HM70)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_HM75)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_HM76)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_HM77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_MBL_SAMPLE)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_NM70)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_Q75)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_Q77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_QM77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_QS77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_SFF_SAMPLE)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_UM77)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_Z75)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_LPC_Z77)}, {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");