summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/gpio/Kconfig8
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-starfive-jh7110.c798
3 files changed, 807 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index e3af86f06c63..e61a69743465 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -567,6 +567,14 @@ config GPIO_SIFIVE
help
Say yes here to support the GPIO device on SiFive SoCs.
+config GPIO_STARFIVE_JH7110
+ bool "Starfive JH7110 GPIO support"
+ depends on SOC_STARFIVE_JH7110
+ depends on OF_GPIO
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support the GPIO device on Starfive VIC7110 SoCs.
+
config GPIO_SIOX
tristate "SIOX GPIO support"
depends on SIOX
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 29e3beb6548c..c64f79170197 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -134,6 +134,7 @@ obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o
obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
+obj-$(CONFIG_GPIO_STARFIVE_JH7110) += gpio-starfive-jh7110.o
obj-$(CONFIG_GPIO_SIM) += gpio-sim.o
obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o
obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o
diff --git a/drivers/gpio/gpio-starfive-jh7110.c b/drivers/gpio/gpio-starfive-jh7110.c
new file mode 100644
index 000000000000..b4ff1369a9e8
--- /dev/null
+++ b/drivers/gpio/gpio-starfive-jh7110.c
@@ -0,0 +1,798 @@
+/*
+ ******************************************************************************
+ * @file gpio-starfive-vic7110.c
+ * @author StarFive Technology
+ * @version V1.0
+ * @date 08/13/2020
+ * @brief
+ ******************************************************************************
+ * @copy
+ *
+ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
+ * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
+ * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY
+ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
+ * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
+ * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+ *
+ * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/spinlock.h>
+
+#define GPIO_EN 0xdc
+#define GPIO_IS_LOW 0xe0
+#define GPIO_IS_HIGH 0xe4
+#define GPIO_IC_LOW 0xe8
+#define GPIO_IC_HIGH 0xec
+#define GPIO_IBE_LOW 0xf0
+#define GPIO_IBE_HIGH 0xf4
+#define GPIO_IEV_LOW 0xf8
+#define GPIO_IEV_HIGH 0xfc
+#define GPIO_IE_LOW 0x100
+#define GPIO_IE_HIGH 0x104
+
+//read only
+#define GPIO_RIS_LOW 0x108
+#define GPIO_RIS_HIGH 0x10c
+#define GPIO_MIS_LOW 0x110
+#define GPIO_MIS_HIGH 0x114
+#define GPIO_DIN_LOW 0x118
+#define GPIO_DIN_HIGH 0x11c
+
+#define GPIO_DOEN_X_REG 0x0
+#define GPIO_DOUT_X_REG 0x40
+
+#define GPIO_INPUT_ENABLE_X_REG 0x120
+
+#define MAX_GPIO 64
+
+#define PROC_VIC "vic_gpio7110"
+
+struct sfvic7110_gpio {
+ raw_spinlock_t lock;
+ void __iomem *base;
+ struct gpio_chip gc;
+ unsigned long enabled;
+ unsigned trigger[MAX_GPIO];
+ unsigned int irq_parent[MAX_GPIO];
+ struct sfvic7110_gpio *self_ptr[MAX_GPIO];
+};
+
+/* lock for procfs read access */
+static DEFINE_MUTEX(read_lock);
+
+/* lock for procfs write access */
+static DEFINE_MUTEX(write_lock);
+
+static DEFINE_SPINLOCK(sfg_lock);
+
+static void __iomem *gpio_base = NULL;
+
+static int sfvic7110_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned int v;
+
+ if (offset >= gc->ngpio)
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&chip->lock, flags);
+ v = readl_relaxed(chip->base + GPIO_DOEN_X_REG + (offset & ~0x3));
+ v &= ~(0x3f << ((offset & 0x3) * 8));
+ v |= 1 << ((offset & 0x3) * 8);
+ writel_relaxed(v, chip->base + GPIO_DOEN_X_REG + (offset & ~0x3));
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static int sfvic7110_direction_output(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned int v;
+
+ if (offset >= gc->ngpio)
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&chip->lock, flags);
+ v = readl_relaxed(chip->base + GPIO_DOEN_X_REG + (offset & ~0x3));
+ v &= ~(0x3f << ((offset & 0x3) * 8));
+ writel_relaxed(v, chip->base + GPIO_DOEN_X_REG + (offset & ~0x3));
+
+ v = readl_relaxed(chip->base + GPIO_DOUT_X_REG + (offset & ~0x3));
+ v &= ~(0x7f << ((offset & 0x3) * 8));
+ v |= value << ((offset & 0x3) * 8);
+ writel_relaxed(v, chip->base + GPIO_DOUT_X_REG + (offset & ~0x3));
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
+
+ return 0;
+}
+
+static int sfvic7110_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned int v;
+
+ if (offset >= gc->ngpio)
+ return -EINVAL;
+
+ v = readl_relaxed(chip->base + GPIO_DOEN_X_REG + (offset & ~0x3));
+ return !!(v & (0x3f << ((offset & 0x3) * 8)));
+}
+
+static int sfvic7110_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ int value;
+
+ if (offset >= gc->ngpio)
+ return -EINVAL;
+
+ if(offset < 32){
+ value = readl_relaxed(chip->base + GPIO_DIN_LOW);
+ return (value >> offset) & 0x1;
+ } else {
+ value = readl_relaxed(chip->base + GPIO_DIN_HIGH);
+ return (value >> (offset - 32)) & 0x1;
+ }
+}
+
+static void sfvic7110_set_value(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned long flags;
+ unsigned int v;
+
+ if (offset >= gc->ngpio)
+ return;
+
+ raw_spin_lock_irqsave(&chip->lock, flags);
+ v = readl_relaxed(chip->base + GPIO_DOUT_X_REG + (offset & ~0x3));
+ v &= ~(0x7f << ((offset & 0x3) * 8));
+ v |= value << ((offset & 0x3) * 8);
+ writel_relaxed(v, chip->base + GPIO_DOUT_X_REG + (offset & ~0x3));
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static void sfvic7110_set_ie(struct sfvic7110_gpio *chip, int offset)
+{
+ unsigned long flags;
+ int old_value, new_value;
+ int reg_offset, index;
+
+ if(offset < 32) {
+ reg_offset = 0;
+ index = offset;
+ } else {
+ reg_offset = 4;
+ index = offset - 32;
+ }
+ raw_spin_lock_irqsave(&chip->lock, flags);
+ old_value = readl_relaxed(chip->base + GPIO_IE_LOW + reg_offset);
+ new_value = old_value | ( 1 << index);
+ writel_relaxed(new_value, chip->base + GPIO_IE_LOW + reg_offset);
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+static int sfvic7110_irq_set_type(struct irq_data *d, unsigned trigger)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d);
+ unsigned int reg_is, reg_ibe, reg_iev;
+ int reg_offset, index;
+
+ if (offset < 0 || offset >= gc->ngpio)
+ return -EINVAL;
+
+ if(offset < 32) {
+ reg_offset = 0;
+ index = offset;
+ } else {
+ reg_offset = 4;
+ index = offset - 32;
+ }
+ switch(trigger) {
+ case IRQ_TYPE_LEVEL_HIGH:
+ reg_is = readl_relaxed(chip->base + GPIO_IS_LOW + reg_offset);
+ reg_ibe = readl_relaxed(chip->base + GPIO_IBE_LOW + reg_offset);
+ reg_iev = readl_relaxed(chip->base + GPIO_IEV_LOW + reg_offset);
+ reg_is &= (~(0x1<< index));
+ reg_ibe &= (~(0x1<< index));
+ reg_iev |= (~(0x1<< index));
+ writel_relaxed(reg_is, chip->base + GPIO_IS_LOW + reg_offset);
+ writel_relaxed(reg_ibe, chip->base + GPIO_IBE_LOW + reg_offset);
+ writel_relaxed(reg_iev, chip->base + GPIO_IEV_LOW + reg_offset);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ reg_is = readl_relaxed(chip->base + GPIO_IS_LOW + reg_offset);
+ reg_ibe = readl_relaxed(chip->base + GPIO_IBE_LOW + reg_offset);
+ reg_iev = readl_relaxed(chip->base + GPIO_IEV_LOW + reg_offset);
+ reg_is &= (~(0x1<< index));
+ reg_ibe &= (~(0x1<< index));
+ reg_iev &= (0x1<< index);
+ writel_relaxed(reg_is, chip->base + GPIO_IS_LOW + reg_offset);
+ writel_relaxed(reg_ibe, chip->base + GPIO_IBE_LOW + reg_offset);
+ writel_relaxed(reg_iev, chip->base + GPIO_IEV_LOW + reg_offset);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ reg_is = readl_relaxed(chip->base + GPIO_IS_LOW + reg_offset);
+ reg_ibe = readl_relaxed(chip->base + GPIO_IBE_LOW + reg_offset);
+ reg_iev = readl_relaxed(chip->base + GPIO_IEV_LOW + reg_offset);
+ reg_is |= (0x1<< index);
+ reg_ibe |= (0x1<< index);
+ reg_iev |= (~(0x1<< index));
+ writel_relaxed(reg_is, chip->base + GPIO_IS_LOW + reg_offset);
+ writel_relaxed(reg_ibe, chip->base + GPIO_IBE_LOW + reg_offset);
+ writel_relaxed(reg_iev, chip->base + GPIO_IEV_LOW + reg_offset);
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ reg_is = readl_relaxed(chip->base + GPIO_IS_LOW + reg_offset);
+ reg_ibe = readl_relaxed(chip->base + GPIO_IBE_LOW + reg_offset);
+ reg_iev = readl_relaxed(chip->base + GPIO_IEV_LOW + reg_offset);
+ reg_is |= (0x1<< index);
+ reg_ibe &= (~(0x1<< index));
+ reg_iev |= (0x1<< index);
+ writel_relaxed(reg_is, chip->base + GPIO_IS_LOW + reg_offset);
+ writel_relaxed(reg_ibe, chip->base + GPIO_IBE_LOW + reg_offset);
+ writel_relaxed(reg_iev, chip->base + GPIO_IEV_LOW + reg_offset);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ reg_is = readl_relaxed(chip->base + GPIO_IS_LOW + reg_offset);
+ reg_ibe = readl_relaxed(chip->base + GPIO_IBE_LOW + reg_offset);
+ reg_iev = readl_relaxed(chip->base + GPIO_IEV_LOW + reg_offset);
+ reg_is |= (0x1<< index);
+ reg_ibe &= (~(0x1<< index));
+ reg_iev &= (~(0x1<< index));
+ writel_relaxed(reg_is, chip->base + GPIO_IS_LOW + reg_offset);
+ writel_relaxed(reg_ibe, chip->base + GPIO_IBE_LOW + reg_offset);
+ writel_relaxed(reg_iev, chip->base + GPIO_IEV_LOW + reg_offset);
+ break;
+ }
+
+ chip->trigger[offset] = trigger;
+ sfvic7110_set_ie(chip, offset);
+ return 0;
+}
+
+/* chained_irq_{enter,exit} already mask the parent */
+static void sfvic7110_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned int value;
+ int offset = irqd_to_hwirq(d);
+ int reg_offset, index;
+
+ if (offset < 0 || offset >= gc->ngpio)
+ return;
+
+ if(offset < 32) {
+ reg_offset = 0;
+ index = offset;
+ } else {
+ reg_offset = 4;
+ index = offset - 32;
+ }
+
+ value = readl_relaxed(chip->base + GPIO_IE_LOW + reg_offset);
+ value &= ~(0x1 << index);
+ writel_relaxed(value,chip->base + GPIO_IE_LOW + reg_offset);
+}
+
+static void sfvic7110_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ unsigned int value;
+ int offset = irqd_to_hwirq(d);
+ int reg_offset, index;
+
+ if (offset < 0 || offset >= gc->ngpio)
+ return;
+
+ if(offset < 32) {
+ reg_offset = 0;
+ index = offset;
+ } else {
+ reg_offset = 4;
+ index = offset - 32;
+ }
+
+ value = readl_relaxed(chip->base + GPIO_IE_LOW + reg_offset);
+ value |= (0x1 << index);
+ writel_relaxed(value,chip->base + GPIO_IE_LOW + reg_offset);
+}
+
+static void sfvic7110_irq_enable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d);
+
+ sfvic7110_irq_unmask(d);
+ assign_bit(offset, &chip->enabled, 1);
+}
+
+static void sfvic7110_irq_disable(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct sfvic7110_gpio *chip = gpiochip_get_data(gc);
+ int offset = irqd_to_hwirq(d) % MAX_GPIO; // must not fail
+
+ assign_bit(offset, &chip->enabled, 0);
+ sfvic7110_set_ie(chip, offset);
+}
+
+static struct irq_chip sfvic7110_irqchip = {
+ .name = "sfvic7110-gpio",
+ .irq_set_type = sfvic7110_irq_set_type,
+ .irq_mask = sfvic7110_irq_mask,
+ .irq_unmask = sfvic7110_irq_unmask,
+ .irq_enable = sfvic7110_irq_enable,
+ .irq_disable = sfvic7110_irq_disable,
+};
+
+
+static int starfive_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ struct sfvic7110_gpio *chip;// = gpiochip_get_data(gc);
+ struct irq_data *d;// = irq_get_irq_data(chip->irq_parent[child]);
+
+#if 0
+ chip = gpiochip_get_data(gc);
+ d = irq_get_irq_data(chip->irq_parent[child]);
+ *parent_type = IRQ_TYPE_NONE;
+ *parent = irqd_to_hwirq(d);
+#endif
+ return 0;
+}
+
+static irqreturn_t sfvic7110_irq_handler(int irq, void *gc)
+{
+ int offset;
+ // = self_ptr - &chip->self_ptr[0];
+ int reg_offset, index;
+ unsigned int value;
+ unsigned long flags;
+ struct sfvic7110_gpio *chip = gc;
+
+ for (offset = 0; offset < 64; offset++) {
+ if(offset < 32) {
+ reg_offset = 0;
+ index = offset;
+ } else {
+ reg_offset = 4;
+ index = offset - 32;
+ }
+
+ raw_spin_lock_irqsave(&chip->lock, flags);
+ value = readl_relaxed(chip->base + GPIO_MIS_LOW + reg_offset);
+ if(value & BIT(index))
+ writel_relaxed(BIT(index), chip->base + GPIO_IC_LOW +
+ reg_offset);
+
+ //generic_handle_irq(irq_find_mapping(chip->gc.irq.domain,
+ // offset));
+ raw_spin_unlock_irqrestore(&chip->lock, flags);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void sf_vic_gpio_dout_reverse(int gpio,int en)
+{
+ unsigned int value;
+ int offset;
+
+ if(!gpio_base)
+ return;
+
+ offset = GPIO_DOUT_X_REG + (gpio & ~0x3);
+
+ spin_lock(&sfg_lock);
+ value = ioread32(gpio_base + offset);
+ value &= ~(0x7f << ((offset & 0x3) * 8));
+ value |= (en & 0x1) << ((offset & 0x3) * 8);
+ iowrite32(value, gpio_base + offset);
+ spin_unlock(&sfg_lock);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_dout_reverse);
+
+void sf_vic_gpio_dout_value(int gpio,int v)
+{
+ unsigned int value;
+ int offset;
+
+ if(!gpio_base)
+ return;
+
+ offset = GPIO_DOUT_X_REG + (gpio & ~0x3);
+
+ spin_lock(&sfg_lock);
+ value = ioread32(gpio_base + offset);
+ value &= ~(0x7f << ((offset & 0x3) * 8));
+ value |= (v & 0x7f) << ((offset & 0x3) * 8);
+ iowrite32(value,gpio_base + offset);
+ spin_unlock(&sfg_lock);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_dout_value);
+
+void sf_vic_gpio_dout_low(int gpio)
+{
+ sf_vic_gpio_dout_value(gpio, 0);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_dout_low);
+
+void sf_vic_gpio_dout_high(int gpio)
+{
+ sf_vic_gpio_dout_value(gpio, 1);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_dout_high);
+
+void sf_vic_gpio_doen_reverse(int gpio,int en)
+{
+ unsigned int value;
+ int offset;
+
+ if(!gpio_base)
+ return;
+
+ offset = GPIO_DOEN_X_REG + (gpio & ~0x3);
+
+ spin_lock(&sfg_lock);
+ value = ioread32(gpio_base + offset);
+ value &= ~(0x3f << ((offset & 0x3) * 8));
+ value |= (en & 0x1) << ((offset & 0x3) * 8);
+ iowrite32(value,gpio_base + offset);
+ spin_unlock(&sfg_lock);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_doen_reverse);
+
+void sf_vic_gpio_doen_value(int gpio,int v)
+{
+ unsigned int value;
+ int offset;
+
+ if(!gpio_base)
+ return;
+
+ offset = GPIO_DOEN_X_REG + (gpio & ~0x3);
+
+ spin_lock(&sfg_lock);
+ value = ioread32(gpio_base + offset);
+ value &= ~(0x3f << ((offset & 0x3) * 8));
+ value |= (v & 0x3f) << ((offset & 0x3) * 8);
+ iowrite32(value,gpio_base + offset);
+ spin_unlock(&sfg_lock);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_doen_value);
+
+void sf_vic_gpio_doen_low(int gpio)
+{
+ sf_vic_gpio_doen_value(gpio, 0);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_doen_low);
+
+void sf_vic_gpio_doen_high(int gpio)
+{
+ sf_vic_gpio_doen_value(gpio, 1);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_doen_high);
+
+void sf_vic_gpio_manual(int offset,int v)
+{
+ unsigned int value;
+
+ if(!gpio_base)
+ return ;
+
+ spin_lock(&sfg_lock);
+ value = ioread32(gpio_base + offset);
+ value &= ~(0xFF);
+ value |= (v&0xFF);
+ iowrite32(value,gpio_base + offset);
+ spin_unlock(&sfg_lock);
+}
+EXPORT_SYMBOL_GPL(sf_vic_gpio_manual);
+
+static int str_to_num(char *str)
+{
+ char *p = str;
+ int value = 0;
+
+ if((*p == '0') && (*(p + 1) == 'x' || *(p + 1) == 'X')) {
+ p = p + 2;
+ while(((*p >= '0') && (*p <= '9')) ||
+ ((*p >= 'a') && (*p <= 'f')) ||
+ ((*p >= 'A') && (*p <= 'F'))) {
+ if((*p >= '0') && (*p <= '9'))
+ value = value * 16 + (*p - '0');
+ if((*p >= 'a') && (*p <= 'f'))
+ value = value * 16 + 10 + (*p - 'a');
+ if((*p >= 'A') && (*p <= 'F'))
+ value = value * 16 + 10 + (*p - 'A');
+ p = p + 1;
+ }
+ } else {
+ while((*p >= '0') && (*p <= '9')) {
+ value = value * 10 + (*p - '0');
+ p = p + 1;
+ }
+ }
+
+ if(*p != '\0')
+ return -EFAULT;
+
+ return value;
+}
+
+static ssize_t vic_gpio_proc_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ char message[64], cmd[8],gnum[8],v[8];
+ int gpionum, value;
+
+ if (mutex_lock_interruptible(&write_lock))
+ return -ERESTARTSYS;
+
+ ret = copy_from_user(message, buf, count);
+ mutex_unlock(&write_lock);
+ if(ret)
+ return -EFAULT;
+ sscanf(message, "%s %s %s", cmd, gnum, v);
+ gpionum = str_to_num(gnum);
+ if(gpionum < 0)
+ return -EFAULT;
+ value = str_to_num(v);
+ if(value < 0)
+ return -EFAULT;
+
+ if(!strcmp(cmd,"dout")) {
+ if(gpionum < 0 || gpionum > 63){
+ printk(KERN_ERR "vic-gpio: dout gpionum (0-63) value (0/1) invalid: gpionum = %d value = %d\n",
+ gpionum,value);
+ return -EFAULT;
+ }
+ sf_vic_gpio_dout_value(gpionum, value);
+ }else if(!strcmp(cmd,"doen")) {
+ if(gpionum < 0 || gpionum > 63){
+ printk(KERN_ERR "vic-gpio: doen gpionum (0-63) value (0/1) invalid: gpionum = %d value = %d\n",
+ gpionum,value);
+ return -EFAULT;
+ }
+ sf_vic_gpio_doen_value(gpionum,value);
+ }else if(!strcmp(cmd,"utrv")) {
+ if(gpionum < 0 || gpionum > 63){
+ printk(KERN_ERR "vic-gpio: utrv gpionum (0-63) is invalid: %d\n",gpionum);
+ return -EFAULT;
+ }
+ sf_vic_gpio_doen_reverse(gpionum,value);
+ }else if(!strcmp(cmd,"enrv")) {
+ if(gpionum < 0 || gpionum > 63){
+ printk(KERN_ERR "vic-gpio: enrv gpionum (0-63) is invalid: %d\n",gpionum);
+ return -EFAULT;
+ }
+ sf_vic_gpio_doen_reverse(gpionum, value);
+ }else if(!strcmp(cmd,"manu")) {
+ if(gpionum < 0x250 || gpionum > 0x378 || (gpionum & 0x3)){
+ printk(KERN_ERR "vic-gpio: manu offset (0x250-0x378 & mod 4) is invalid: %d\n",gpionum);
+ return -EFAULT;
+ }
+ sf_vic_gpio_manual(gpionum, value);
+ }else {
+ printk(KERN_ERR "vic-gpio: cmd (dout doen utrv enrv manu) invalid: %s\n",cmd);
+ }
+
+ return count;
+}
+
+static ssize_t vic_gpio_proc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret;
+ unsigned int copied;
+ char message[256];
+
+ sprintf(message, "Usage: echo 'cmd gpionum value' >/proc/vic_gpio\n\t"
+ "cmd: dout doen utrv enrv or manu\n\t"
+ "gpionum: gpionum or address offset for manu\n\t"
+ "value: 0/1 for utrv/enrv, value for dout/doen/manual\n");
+ copied = strlen(message);
+
+ if(*ppos >= copied)
+ return 0;
+
+ if (mutex_lock_interruptible(&read_lock))
+ return -ERESTARTSYS;
+
+ ret = copy_to_user(buf, message, copied);
+ if(ret) {
+ mutex_unlock(&read_lock);
+ }
+ *ppos += copied;
+
+ mutex_unlock(&read_lock);
+
+ return copied;
+}
+
+static const struct file_operations vic_gpio_fops = {
+ .owner = THIS_MODULE,
+ .read = vic_gpio_proc_read,
+ .write = vic_gpio_proc_write,
+ .llseek = noop_llseek,
+};
+
+static int sfvic7110_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sfvic7110_gpio *chip;
+ struct resource *res;
+ int irq, ret, ngpio;
+ int loop;
+ struct gpio_irq_chip *girq;
+ struct device_node *node = pdev->dev.of_node;
+ struct device_node *irq_parent;
+ struct irq_domain *parent;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(dev, "out of memory\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ chip->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(chip->base)) {
+ dev_err(dev, "failed to allocate device memory\n");
+ return PTR_ERR(chip->base);
+ }
+ gpio_base = chip->base ;
+
+ ngpio = 64;
+
+ raw_spin_lock_init(&chip->lock);
+ chip->gc.direction_input = sfvic7110_direction_input;
+ chip->gc.direction_output = sfvic7110_direction_output;
+ chip->gc.get_direction = sfvic7110_get_direction;
+ chip->gc.get = sfvic7110_get_value;
+ chip->gc.set = sfvic7110_set_value;
+ chip->gc.base = 0;
+ chip->gc.ngpio = ngpio;
+ chip->gc.label = dev_name(dev);
+ chip->gc.parent = dev;
+ chip->gc.owner = THIS_MODULE;
+
+ irq_parent = of_irq_find_parent(node);
+ if (!irq_parent) {
+ dev_err(dev, "no IRQ parent node\n");
+ return -ENODEV;
+ }
+ parent = irq_find_host(irq_parent);
+ if (!parent) {
+ dev_err(dev, "no IRQ parent domain\n");
+ return -ENODEV;
+ }
+
+ girq = &chip->gc.irq;
+ girq->chip = &sfvic7110_irqchip;
+ girq->fwnode = of_node_to_fwnode(node);
+ girq->parent_domain = parent;
+ girq->child_to_parent_hwirq = starfive_gpio_child_to_parent_hwirq;
+ girq->handler = handle_simple_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+
+ /* Disable all GPIO interrupts before enabling parent interrupts */
+ iowrite32(0, chip->base + GPIO_IE_HIGH);
+ iowrite32(0, chip->base + GPIO_IE_LOW);
+ chip->enabled = 0;
+
+ platform_set_drvdata(pdev, chip);
+
+ ret = gpiochip_add_data(&chip->gc, chip);
+ if (ret){
+ dev_err(dev, "gpiochip_add_data ret=%d!\n", ret);
+ return ret;
+ }
+
+#if 0
+ /* Disable all GPIO interrupts before enabling parent interrupts */
+ iowrite32(0, chip->base + GPIO_IE_HIGH);
+ iowrite32(0, chip->base + GPIO_IE_LOW);
+ chip->enabled = 0;
+
+ ret = gpiochip_irqchip_add(&chip->gc, &sfvic7110_irqchip, 0,
+ handle_simple_irq, IRQ_TYPE_NONE);
+ if (ret) {
+ dev_err(dev, "could not add irqchip\n");
+ gpiochip_remove(&chip->gc);
+ return ret;
+ }
+#endif
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "Cannot get IRQ resource\n");
+ return irq;
+ }
+
+ chip->irq_parent[0] = irq;
+
+ ret = devm_request_irq(dev, irq, sfvic7110_irq_handler, IRQF_SHARED,
+ dev_name(dev), chip);
+ if (ret) {
+ dev_err(dev, "IRQ handler registering failed (%d)\n", ret);
+ return ret;
+ }
+
+ writel_relaxed(1, chip->base + GPIO_EN);
+
+ for(loop = 0; loop < MAX_GPIO; loop++) {
+ unsigned int v;
+ v = readl_relaxed(chip->base + GPIO_INPUT_ENABLE_X_REG + (loop << 2));
+ v |= 0x1;
+ writel_relaxed(v, chip->base + GPIO_INPUT_ENABLE_X_REG + (loop << 2));
+ }
+
+ if (proc_create(PROC_VIC, 0, NULL, (void *)&vic_gpio_fops) == NULL) {
+ return -ENOMEM;
+ }
+ dev_info(dev, "SiFive GPIO chip registered %d GPIOs\n", ngpio);
+
+ return 0;
+}
+
+static const struct of_device_id sfvic7110_gpio_match[] = {
+ { .compatible = "starfive,gpio7110", },
+ { },
+};
+
+static struct platform_driver sfvic7110_gpio_driver = {
+ .probe = sfvic7110_gpio_probe,
+ .driver = {
+ .name = "sfvic7110_gpio",
+ .of_match_table = of_match_ptr(sfvic7110_gpio_match),
+ },
+};
+
+static int __init sfvic7110_gpio_init(void)
+{
+ return platform_driver_register(&sfvic7110_gpio_driver);
+}
+subsys_initcall(sfvic7110_gpio_init);
+
+static void __exit sfvic7110_gpio_exit(void)
+{
+ platform_driver_unregister(&sfvic7110_gpio_driver);
+}
+module_exit(sfvic7110_gpio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huan Feng <huan.feng@starfivetech.com>");
+MODULE_DESCRIPTION("Starfive VIC GPIO generator driver");