| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * GPIO interface for Intel Poulsbo SCH | |
| * | |
| * Copyright (c) 2010 CompuLab Ltd | |
| * Author: Denis Turischev <denis@compulab.co.il> | |
| */ | |
| #include <linux/acpi.h> | |
| #include <linux/bitops.h> | |
| #include <linux/errno.h> | |
| #include <linux/gpio/driver.h> | |
| #include <linux/io.h> | |
| #include <linux/irq.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/pci_ids.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/types.h> | |
| #define GEN 0x00 | |
| #define GIO 0x04 | |
| #define GLV 0x08 | |
| #define GTPE 0x0c | |
| #define GTNE 0x10 | |
| #define GGPE 0x14 | |
| #define GSMI 0x18 | |
| #define GTS 0x1c | |
| #define CORE_BANK_OFFSET 0x00 | |
| #define RESUME_BANK_OFFSET 0x20 | |
| /* | |
| * iLB datasheet describes GPE0BLK registers, in particular GPE0E.GPIO bit. | |
| * Document Number: 328195-001 | |
| */ | |
| #define GPE0E_GPIO 14 | |
| struct sch_gpio { | |
| struct gpio_chip chip; | |
| void __iomem *regs; | |
| raw_spinlock_t lock; | |
| unsigned short resume_base; | |
| /* GPE handling */ | |
| u32 gpe; | |
| acpi_gpe_handler gpe_handler; | |
| }; | |
| static unsigned int sch_gpio_offset(struct sch_gpio *sch, unsigned int gpio, | |
| unsigned int reg) | |
| { | |
| unsigned int base = CORE_BANK_OFFSET; | |
| if (gpio >= sch->resume_base) { | |
| gpio -= sch->resume_base; | |
| base = RESUME_BANK_OFFSET; | |
| } | |
| return base + reg + gpio / 8; | |
| } | |
| static unsigned int sch_gpio_bit(struct sch_gpio *sch, unsigned int gpio) | |
| { | |
| if (gpio >= sch->resume_base) | |
| gpio -= sch->resume_base; | |
| return gpio % 8; | |
| } | |
| static int sch_gpio_reg_get(struct sch_gpio *sch, unsigned int gpio, unsigned int reg) | |
| { | |
| unsigned short offset, bit; | |
| u8 reg_val; | |
| offset = sch_gpio_offset(sch, gpio, reg); | |
| bit = sch_gpio_bit(sch, gpio); | |
| reg_val = !!(ioread8(sch->regs + offset) & BIT(bit)); | |
| return reg_val; | |
| } | |
| static void sch_gpio_reg_set(struct sch_gpio *sch, unsigned int gpio, unsigned int reg, | |
| int val) | |
| { | |
| unsigned short offset, bit; | |
| u8 reg_val; | |
| offset = sch_gpio_offset(sch, gpio, reg); | |
| bit = sch_gpio_bit(sch, gpio); | |
| reg_val = ioread8(sch->regs + offset); | |
| if (val) | |
| reg_val |= BIT(bit); | |
| else | |
| reg_val &= ~BIT(bit); | |
| iowrite8(reg_val, sch->regs + offset); | |
| } | |
| static int sch_gpio_direction_in(struct gpio_chip *gc, unsigned int gpio_num) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| unsigned long flags; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GIO, 1); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| return 0; | |
| } | |
| static int sch_gpio_get(struct gpio_chip *gc, unsigned int gpio_num) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| return sch_gpio_reg_get(sch, gpio_num, GLV); | |
| } | |
| static int sch_gpio_set(struct gpio_chip *gc, unsigned int gpio_num, int val) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| unsigned long flags; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GLV, val); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| return 0; | |
| } | |
| static int sch_gpio_direction_out(struct gpio_chip *gc, unsigned int gpio_num, | |
| int val) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| unsigned long flags; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GIO, 0); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| /* | |
| * according to the datasheet, writing to the level register has no | |
| * effect when GPIO is programmed as input. | |
| * Actually the level register is read-only when configured as input. | |
| * Thus presetting the output level before switching to output is _NOT_ possible. | |
| * Hence we set the level after configuring the GPIO as output. | |
| * But we cannot prevent a short low pulse if direction is set to high | |
| * and an external pull-up is connected. | |
| */ | |
| return sch_gpio_set(gc, gpio_num, val); | |
| } | |
| static int sch_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio_num) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| if (sch_gpio_reg_get(sch, gpio_num, GIO)) | |
| return GPIO_LINE_DIRECTION_IN; | |
| return GPIO_LINE_DIRECTION_OUT; | |
| } | |
| static const struct gpio_chip sch_gpio_chip = { | |
| .label = "sch_gpio", | |
| .owner = THIS_MODULE, | |
| .direction_input = sch_gpio_direction_in, | |
| .get = sch_gpio_get, | |
| .direction_output = sch_gpio_direction_out, | |
| .set = sch_gpio_set, | |
| .get_direction = sch_gpio_get_direction, | |
| }; | |
| static int sch_irq_type(struct irq_data *d, unsigned int type) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| irq_hw_number_t gpio_num = irqd_to_hwirq(d); | |
| unsigned long flags; | |
| int rising, falling; | |
| switch (type & IRQ_TYPE_SENSE_MASK) { | |
| case IRQ_TYPE_EDGE_RISING: | |
| rising = 1; | |
| falling = 0; | |
| break; | |
| case IRQ_TYPE_EDGE_FALLING: | |
| rising = 0; | |
| falling = 1; | |
| break; | |
| case IRQ_TYPE_EDGE_BOTH: | |
| rising = 1; | |
| falling = 1; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GTPE, rising); | |
| sch_gpio_reg_set(sch, gpio_num, GTNE, falling); | |
| irq_set_handler_locked(d, handle_edge_irq); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| return 0; | |
| } | |
| static void sch_irq_ack(struct irq_data *d) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| irq_hw_number_t gpio_num = irqd_to_hwirq(d); | |
| unsigned long flags; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GTS, 1); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| } | |
| static void sch_irq_mask_unmask(struct gpio_chip *gc, irq_hw_number_t gpio_num, int val) | |
| { | |
| struct sch_gpio *sch = gpiochip_get_data(gc); | |
| unsigned long flags; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| sch_gpio_reg_set(sch, gpio_num, GGPE, val); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| } | |
| static void sch_irq_mask(struct irq_data *d) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | |
| irq_hw_number_t gpio_num = irqd_to_hwirq(d); | |
| sch_irq_mask_unmask(gc, gpio_num, 0); | |
| gpiochip_disable_irq(gc, gpio_num); | |
| } | |
| static void sch_irq_unmask(struct irq_data *d) | |
| { | |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | |
| irq_hw_number_t gpio_num = irqd_to_hwirq(d); | |
| gpiochip_enable_irq(gc, gpio_num); | |
| sch_irq_mask_unmask(gc, gpio_num, 1); | |
| } | |
| static const struct irq_chip sch_irqchip = { | |
| .name = "sch_gpio", | |
| .irq_ack = sch_irq_ack, | |
| .irq_mask = sch_irq_mask, | |
| .irq_unmask = sch_irq_unmask, | |
| .irq_set_type = sch_irq_type, | |
| .flags = IRQCHIP_IMMUTABLE, | |
| GPIOCHIP_IRQ_RESOURCE_HELPERS, | |
| }; | |
| static u32 sch_gpio_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context) | |
| { | |
| struct sch_gpio *sch = context; | |
| struct gpio_chip *gc = &sch->chip; | |
| unsigned long core_status, resume_status; | |
| unsigned long pending; | |
| unsigned long flags; | |
| int offset; | |
| u32 ret; | |
| raw_spin_lock_irqsave(&sch->lock, flags); | |
| core_status = ioread32(sch->regs + CORE_BANK_OFFSET + GTS); | |
| resume_status = ioread32(sch->regs + RESUME_BANK_OFFSET + GTS); | |
| raw_spin_unlock_irqrestore(&sch->lock, flags); | |
| pending = (resume_status << sch->resume_base) | core_status; | |
| for_each_set_bit(offset, &pending, sch->chip.ngpio) | |
| generic_handle_domain_irq(gc->irq.domain, offset); | |
| /* Set returning value depending on whether we handled an interrupt */ | |
| ret = pending ? ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED; | |
| /* Acknowledge GPE to ACPICA */ | |
| ret |= ACPI_REENABLE_GPE; | |
| return ret; | |
| } | |
| static void sch_gpio_remove_gpe_handler(void *data) | |
| { | |
| struct sch_gpio *sch = data; | |
| acpi_disable_gpe(NULL, sch->gpe); | |
| acpi_remove_gpe_handler(NULL, sch->gpe, sch->gpe_handler); | |
| } | |
| static int sch_gpio_install_gpe_handler(struct sch_gpio *sch) | |
| { | |
| struct device *dev = sch->chip.parent; | |
| acpi_status status; | |
| status = acpi_install_gpe_handler(NULL, sch->gpe, ACPI_GPE_LEVEL_TRIGGERED, | |
| sch->gpe_handler, sch); | |
| if (ACPI_FAILURE(status)) { | |
| dev_err(dev, "Failed to install GPE handler for %u: %s\n", | |
| sch->gpe, acpi_format_exception(status)); | |
| return -ENODEV; | |
| } | |
| status = acpi_enable_gpe(NULL, sch->gpe); | |
| if (ACPI_FAILURE(status)) { | |
| dev_err(dev, "Failed to enable GPE handler for %u: %s\n", | |
| sch->gpe, acpi_format_exception(status)); | |
| acpi_remove_gpe_handler(NULL, sch->gpe, sch->gpe_handler); | |
| return -ENODEV; | |
| } | |
| return devm_add_action_or_reset(dev, sch_gpio_remove_gpe_handler, sch); | |
| } | |
| static int sch_gpio_probe(struct platform_device *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct gpio_irq_chip *girq; | |
| struct sch_gpio *sch; | |
| struct resource *res; | |
| void __iomem *regs; | |
| int ret; | |
| sch = devm_kzalloc(dev, sizeof(*sch), GFP_KERNEL); | |
| if (!sch) | |
| return -ENOMEM; | |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
| if (!res) | |
| return -EBUSY; | |
| regs = devm_ioport_map(dev, res->start, resource_size(res)); | |
| if (!regs) | |
| return -EBUSY; | |
| sch->regs = regs; | |
| raw_spin_lock_init(&sch->lock); | |
| sch->chip = sch_gpio_chip; | |
| sch->chip.label = dev_name(dev); | |
| sch->chip.parent = dev; | |
| switch (pdev->id) { | |
| case PCI_DEVICE_ID_INTEL_SCH_LPC: | |
| sch->resume_base = 10; | |
| sch->chip.ngpio = 14; | |
| /* | |
| * GPIO[6:0] enabled by default | |
| * GPIO7 is configured by the CMC as SLPIOVR | |
| * Enable GPIO[9:8] core powered gpios explicitly | |
| */ | |
| sch_gpio_reg_set(sch, 8, GEN, 1); | |
| sch_gpio_reg_set(sch, 9, GEN, 1); | |
| /* | |
| * SUS_GPIO[2:0] enabled by default | |
| * Enable SUS_GPIO3 resume powered gpio explicitly | |
| */ | |
| sch_gpio_reg_set(sch, 13, GEN, 1); | |
| break; | |
| case PCI_DEVICE_ID_INTEL_ITC_LPC: | |
| sch->resume_base = 5; | |
| sch->chip.ngpio = 14; | |
| break; | |
| case PCI_DEVICE_ID_INTEL_CENTERTON_ILB: | |
| sch->resume_base = 21; | |
| sch->chip.ngpio = 30; | |
| break; | |
| case PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB: | |
| sch->resume_base = 2; | |
| sch->chip.ngpio = 8; | |
| break; | |
| default: | |
| return -ENODEV; | |
| } | |
| girq = &sch->chip.irq; | |
| gpio_irq_chip_set_chip(girq, &sch_irqchip); | |
| girq->num_parents = 0; | |
| girq->parents = NULL; | |
| girq->parent_handler = NULL; | |
| girq->default_type = IRQ_TYPE_NONE; | |
| girq->handler = handle_bad_irq; | |
| /* GPE setup is optional */ | |
| sch->gpe = GPE0E_GPIO; | |
| sch->gpe_handler = sch_gpio_gpe_handler; | |
| ret = sch_gpio_install_gpe_handler(sch); | |
| if (ret) | |
| dev_warn(dev, "Can't setup GPE, no IRQ support\n"); | |
| return devm_gpiochip_add_data(dev, &sch->chip, sch); | |
| } | |
| static struct platform_driver sch_gpio_driver = { | |
| .driver = { | |
| .name = "sch_gpio", | |
| }, | |
| .probe = sch_gpio_probe, | |
| }; | |
| module_platform_driver(sch_gpio_driver); | |
| MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); | |
| MODULE_DESCRIPTION("GPIO interface for Intel Poulsbo SCH"); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_ALIAS("platform:sch_gpio"); |