diff options
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/gpio-aspeed.c | 251 |
1 files changed, 231 insertions, 20 deletions
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index b3968f66b1d2..1e00f4045f9d 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c @@ -12,6 +12,7 @@ #include <asm/div64.h> #include <linux/clk.h> #include <linux/gpio/driver.h> +#include <linux/gpio/aspeed.h> #include <linux/hashtable.h> #include <linux/init.h> #include <linux/io.h> @@ -22,6 +23,15 @@ #include <linux/spinlock.h> #include <linux/string.h> +/* + * These two headers aren't meant to be used by GPIO drivers. We need + * them in order to access gpio_chip_hwgpio() which we need to implement + * the aspeed specific API which allows the coprocessor to request + * access to some GPIOs and to arbitrate between coprocessor and ARM. + */ +#include <linux/gpio/consumer.h> +#include "gpiolib.h" + struct aspeed_bank_props { unsigned int bank; u32 input; @@ -56,6 +66,7 @@ struct aspeed_gpio { struct clk *clk; u32 *dcache; + u8 *cf_copro_bankmap; }; struct aspeed_gpio_bank { @@ -83,6 +94,9 @@ struct aspeed_gpio_bank { static const int debounce_timers[4] = { 0x00, 0x50, 0x54, 0x58 }; +static const struct aspeed_gpio_copro_ops *copro_ops; +static void *copro_data; + static const struct aspeed_gpio_bank aspeed_gpio_banks[] = { { .val_regs = 0x0000, @@ -323,6 +337,50 @@ static void aspeed_gpio_change_cmd_source(struct aspeed_gpio *gpio, iowrite32(reg, c0); } +static bool aspeed_gpio_copro_request(struct aspeed_gpio *gpio, + unsigned int offset) +{ + const struct aspeed_gpio_bank *bank = to_bank(offset); + + if (!copro_ops || !gpio->cf_copro_bankmap) + return false; + if (!gpio->cf_copro_bankmap[offset >> 3]) + return false; + if (!copro_ops->request_access) + return false; + + /* Pause the coprocessor */ + copro_ops->request_access(copro_data); + + /* Change command source back to ARM */ + aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, GPIO_CMDSRC_ARM); + + /* Update cache */ + gpio->dcache[GPIO_BANK(offset)] = ioread32(bank_reg(gpio, bank, reg_rdata)); + + return true; +} + +static void aspeed_gpio_copro_release(struct aspeed_gpio *gpio, + unsigned int offset) +{ + const struct aspeed_gpio_bank *bank = to_bank(offset); + + if (!copro_ops || !gpio->cf_copro_bankmap) + return; + if (!gpio->cf_copro_bankmap[offset >> 3]) + return; + if (!copro_ops->release_access) + return; + + /* Change command source back to ColdFire */ + aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, + GPIO_CMDSRC_COLDFIRE); + + /* Restart the coprocessor */ + copro_ops->release_access(copro_data); +} + static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) { struct aspeed_gpio *gpio = gpiochip_get_data(gc); @@ -356,11 +414,15 @@ static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, { struct aspeed_gpio *gpio = gpiochip_get_data(gc); unsigned long flags; + bool copro; spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); __aspeed_gpio_set(gc, offset, val); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -368,7 +430,9 @@ static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) { struct aspeed_gpio *gpio = gpiochip_get_data(gc); const struct aspeed_gpio_bank *bank = to_bank(offset); + void __iomem *addr = bank_reg(gpio, bank, reg_dir); unsigned long flags; + bool copro; u32 reg; if (!have_input(gpio, offset)) @@ -376,8 +440,13 @@ static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) spin_lock_irqsave(&gpio->lock, flags); - reg = ioread32(bank_reg(gpio, bank, reg_dir)); - iowrite32(reg & ~GPIO_BIT(offset), bank_reg(gpio, bank, reg_dir)); + reg = ioread32(addr); + reg &= ~GPIO_BIT(offset); + + copro = aspeed_gpio_copro_request(gpio, offset); + iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); @@ -389,7 +458,9 @@ static int aspeed_gpio_dir_out(struct gpio_chip *gc, { struct aspeed_gpio *gpio = gpiochip_get_data(gc); const struct aspeed_gpio_bank *bank = to_bank(offset); + void __iomem *addr = bank_reg(gpio, bank, reg_dir); unsigned long flags; + bool copro; u32 reg; if (!have_output(gpio, offset)) @@ -397,10 +468,15 @@ static int aspeed_gpio_dir_out(struct gpio_chip *gc, spin_lock_irqsave(&gpio->lock, flags); + reg = ioread32(addr); + reg |= GPIO_BIT(offset); + + copro = aspeed_gpio_copro_request(gpio, offset); __aspeed_gpio_set(gc, offset, val); - reg = ioread32(bank_reg(gpio, bank, reg_dir)); - iowrite32(reg | GPIO_BIT(offset), bank_reg(gpio, bank, reg_dir)); + iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); return 0; @@ -430,24 +506,23 @@ static int aspeed_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) } static inline int irqd_to_aspeed_gpio_data(struct irq_data *d, - struct aspeed_gpio **gpio, - const struct aspeed_gpio_bank **bank, - u32 *bit) + struct aspeed_gpio **gpio, + const struct aspeed_gpio_bank **bank, + u32 *bit, int *offset) { - int offset; struct aspeed_gpio *internal; - offset = irqd_to_hwirq(d); + *offset = irqd_to_hwirq(d); internal = irq_data_get_irq_chip_data(d); /* This might be a bit of a questionable place to check */ - if (!have_irq(internal, offset)) + if (!have_irq(internal, *offset)) return -ENOTSUPP; *gpio = internal; - *bank = to_bank(offset); - *bit = GPIO_BIT(offset); + *bank = to_bank(*offset); + *bit = GPIO_BIT(*offset); return 0; } @@ -458,17 +533,23 @@ static void aspeed_gpio_irq_ack(struct irq_data *d) struct aspeed_gpio *gpio; unsigned long flags; void __iomem *status_addr; + int rc, offset; + bool copro; u32 bit; - int rc; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return; status_addr = bank_reg(gpio, bank, reg_irq_status); spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + iowrite32(bit, status_addr); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -479,15 +560,17 @@ static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) unsigned long flags; u32 reg, bit; void __iomem *addr; - int rc; + int rc, offset; + bool copro; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return; addr = bank_reg(gpio, bank, reg_irq_enable); spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); reg = ioread32(addr); if (set) @@ -496,6 +579,8 @@ static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) reg &= ~bit; iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -520,9 +605,10 @@ static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) struct aspeed_gpio *gpio; unsigned long flags; void __iomem *addr; - int rc; + int rc, offset; + bool copro; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return -EINVAL; @@ -548,6 +634,7 @@ static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) } spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); addr = bank_reg(gpio, bank, reg_irq_type0); reg = ioread32(addr); @@ -564,6 +651,8 @@ static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) reg = (reg & ~bit) | type2; iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); irq_set_handler_locked(d, handler); @@ -658,11 +747,14 @@ static int aspeed_gpio_reset_tolerance(struct gpio_chip *chip, struct aspeed_gpio *gpio = gpiochip_get_data(chip); unsigned long flags; void __iomem *treg; + bool copro; u32 val; treg = bank_reg(gpio, to_bank(offset), reg_tolerance); spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + val = readl(treg); if (enable) @@ -671,6 +763,9 @@ static int aspeed_gpio_reset_tolerance(struct gpio_chip *chip, val &= ~GPIO_BIT(offset); writel(val, treg); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); return 0; @@ -766,6 +861,9 @@ static void configure_timer(struct aspeed_gpio *gpio, unsigned int offset, void __iomem *addr; u32 val; + /* Note: Debounce timer isn't under control of the command + * source registers, so no need to sync with the coprocessor + */ addr = bank_reg(gpio, bank, reg_debounce_sel1); val = ioread32(addr); iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE1(timer, offset), addr); @@ -912,6 +1010,111 @@ static int aspeed_gpio_set_config(struct gpio_chip *chip, unsigned int offset, return -ENOTSUPP; } +/** + * aspeed_gpio_copro_set_ops - Sets the callbacks used for handhsaking with + * the coprocessor for shared GPIO banks + * @ops: The callbacks + * @data: Pointer passed back to the callbacks + */ +int aspeed_gpio_copro_set_ops(const struct aspeed_gpio_copro_ops *ops, void *data) +{ + copro_data = data; + copro_ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_set_ops); + +/** + * aspeed_gpio_copro_grab_gpio - Mark a GPIO used by the coprocessor. The entire + * bank gets marked and any access from the ARM will + * result in handshaking via callbacks. + * @desc: The GPIO to be marked + * @vreg_offset: If non-NULL, returns the value register offset in the GPIO space + * @dreg_offset: If non-NULL, returns the data latch register offset in the GPIO space + * @bit: If non-NULL, returns the bit number of the GPIO in the registers + */ +int aspeed_gpio_copro_grab_gpio(struct gpio_desc *desc, + u16 *vreg_offset, u16 *dreg_offset, u8 *bit) +{ + struct gpio_chip *chip = gpiod_to_chip(desc); + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + + if (!gpio->cf_copro_bankmap) + gpio->cf_copro_bankmap = kzalloc(gpio->config->nr_gpios >> 3, GFP_KERNEL); + if (!gpio->cf_copro_bankmap) + return -ENOMEM; + if (offset < 0 || offset > gpio->config->nr_gpios) + return -EINVAL; + bindex = offset >> 3; + + spin_lock_irqsave(&gpio->lock, flags); + + /* Sanity check, this shouldn't happen */ + if (gpio->cf_copro_bankmap[bindex] == 0xff) { + rc = -EIO; + goto bail; + } + gpio->cf_copro_bankmap[bindex]++; + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 1) + aspeed_gpio_change_cmd_source(gpio, bank, bindex, + GPIO_CMDSRC_COLDFIRE); + + if (vreg_offset) + *vreg_offset = bank->val_regs; + if (dreg_offset) + *dreg_offset = bank->rdata_reg; + if (bit) + *bit = GPIO_OFFSET(offset); + bail: + spin_unlock_irqrestore(&gpio->lock, flags); + return rc; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_grab_gpio); + +/** + * aspeed_gpio_copro_release_gpio - Unmark a GPIO used by the coprocessor. + * @desc: The GPIO to be marked + */ +int aspeed_gpio_copro_release_gpio(struct gpio_desc *desc) +{ + struct gpio_chip *chip = gpiod_to_chip(desc); + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + + if (!gpio->cf_copro_bankmap) + return -ENXIO; + + if (offset < 0 || offset > gpio->config->nr_gpios) + return -EINVAL; + bindex = offset >> 3; + + spin_lock_irqsave(&gpio->lock, flags); + + /* Sanity check, this shouldn't happen */ + if (gpio->cf_copro_bankmap[bindex] == 0) { + rc = -EIO; + goto bail; + } + gpio->cf_copro_bankmap[bindex]--; + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 0) + aspeed_gpio_change_cmd_source(gpio, bank, bindex, + GPIO_CMDSRC_ARM); + bail: + spin_unlock_irqrestore(&gpio->lock, flags); + return rc; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_release_gpio); + /* * Any banks not specified in a struct aspeed_bank_props array are assumed to * have the properties: @@ -1002,10 +1205,18 @@ static int __init aspeed_gpio_probe(struct platform_device *pdev) if (!gpio->dcache) return -ENOMEM; - /* Populate it with initial values read from the HW */ + /* + * Populate it with initial values read from the HW and switch + * all command sources to the ARM by default + */ for (i = 0; i < banks; i++) { - void __iomem *addr = bank_reg(gpio, &aspeed_gpio_banks[i], reg_rdata); + const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; + void __iomem *addr = bank_reg(gpio, bank, reg_rdata); gpio->dcache[i] = ioread32(addr); + aspeed_gpio_change_cmd_source(gpio, bank, 0, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 1, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 2, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 3, GPIO_CMDSRC_ARM); } rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); |