|
@@ -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);
|