GPIO driver support for Phytium desktop and embedded CPUs

GPIO dirver fix patch. Support Phytiumm Desktop and Embedded CPUs, such as D2000 and E2000.

Reviewed-by: Hongbo Mao <maohongbo@phytium.com.cn>
(cherry picked from commit c2a5e2beae)
Signed-off-by: Alex Shi <alexsshi@tencent.com>
Signed-off-by: Jianping Liu <frankjpliu@tencent.com>
This commit is contained in:
liqian 2023-04-26 15:46:06 +08:00 committed by Jianping Liu
parent 865c34dd67
commit 179588c1a5
10 changed files with 1263 additions and 0 deletions

View File

@ -0,0 +1,32 @@
* Phytium SGPIO controller
This SGPIO controller is for Phytium Pe220x SoCs, which supports up to
96 (32x3) Serial GPIOs.
Required properties:
- compatible : Should contain "phytium,gpio"
- reg : Address and length of the register set for the device.
- interrupts: Interrupt mapping for GPIO IRQ.
- gpio-controller : Marks the device node as a gpio controller.
- #gpio-cells : Should be 2. The first cell is the pin number and
the second cell is used to specify the gpio polarity:
0 = active high
1 = active low
- ngpios: number of GPIO lines, see gpio.txt
(should be multiple of 32, up to 96 pins)
- bus-frequency: SGPIO CLK frequency
- clocks: A phandle to the APB clock for SGPIO clock division
Example:
sgpio: sgpio@2807d000 {
compatible = "phytium,sgpio";
reg = <0x0 0x2807d000 0x0 0x1000>;
interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&sysclk_48mhz>;
ngpios = <96>;
bus-frequency = <48000>;
gpio-controller;
#gpio-cells = <2>;
};

View File

@ -0,0 +1,47 @@
* Phytium GPIO controller
Required properties:
- compatible : Should contain "phytium,gpio"
- reg : Address and length of the register set for the device.
- interrupts: Interrupt mapping for GPIO IRQ.
- gpio-controller : Marks the device node as a gpio controller.
- #gpio-cells : Should be 2. The first cell is the pin number and
the second cell is used to specify the gpio polarity:
0 = active high
1 = active low
- #address-cells : should be 1 (for addressing port subnodes).
- #size-cells : should be 0 (port subnodes).
The GPIO controller has two ports, each of which are represented as child
nodes with the following properties:
Required properties:
- compatible : "phytium,gpio-port"
- reg : The integer port index of the port, a single cell.
Optional properties:
- nr-gpios : The number of pins in the port, a single cell.
Example:
gpio: gpio@28004000 {
compatible = "phytium,gpio";
reg = <0x0 0x28004000 0x0 0x1000>;
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
#address-cells = <1>;
#size-cells = <0>;
porta {
compatible = "phytium,gpio-port";
reg = <0>;
nr-gpios = <8>;
};
portb {
compatible = "phytium,gpio-port";
reg = <1>;
nr-gpios = <8>;
};
};

View File

@ -114,6 +114,7 @@ CONFIG_CRYPTO_AES_ARM64_CE_CCM=y
CONFIG_CRYPTO_AES_ARM64_CE_BLK=y
CONFIG_CRYPTO_CHACHA20_NEON=m
CONFIG_CRYPTO_AES_ARM64_BS=m
CONFIG_GPIO_PHYTIUM_SGPIO=y
CONFIG_JUMP_LABEL=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
@ -422,6 +423,8 @@ CONFIG_GPIO_MAX732X=y
CONFIG_GPIO_PCA953X=y
CONFIG_GPIO_PCA953X_IRQ=y
CONFIG_GPIO_MAX77620=y
CONFIG_GPIO_PHYTIUM_PLAT=y
CONFIG_GPIO_PHYTIUM_PCI=m
CONFIG_POWER_AVS=y
CONFIG_ROCKCHIP_IODOMAIN=y
CONFIG_POWER_RESET_MSM=y

View File

@ -75,6 +75,10 @@ config GPIO_GENERIC
# put drivers in the right section, in alphabetical order
# This symbol is selected by both MMIO and PCI expanders
config GPIO_PHYTIUM_CORE
tristate
# This symbol is selected by both I2C and SPI expanders
config GPIO_MAX730X
tristate
@ -417,6 +421,27 @@ config GPIO_OMAP
help
Say yes here to enable GPIO support for TI OMAP SoCs.
config GPIO_PHYTIUM_PLAT
tristate "Phytium GPIO Platform support"
default y if ARCH_PHYTIUM
depends on ARM64
select GPIO_PHYTIUM_CORE
select IRQ_DOMAIN
select GENERIC_IRQ_CHIP
select GPIOLIB_IRQCHIP
help
Say yes here to support the on-chip GPIO controller for the
Phytium SoC family.
config GPIO_PHYTIUM_SGPIO
tristate "Phytium SGPIO support"
default y if ARCH_PHYTIUM
depends on ARM64
select IRQ_DOMAIN
select GENERIC_IRQ_CHIP
help
Say yes here to enable SGPIO support for Phytium SoCs.
config GPIO_PL061
bool "PrimeCell PL061 GPIO support"
depends on ARM_AMBA
@ -1403,6 +1428,19 @@ config GPIO_PCIE_IDIO_24
Input filter control is not supported by this driver, and the input
filters are deactivated by this driver.
config GPIO_PHYTIUM_PCI
tristate "Phytium GPIO PCI support"
select GPIO_PHYTIUM_CORE
select IRQ_DOMAIN
select GENERIC_IRQ_CHIP
select GPIOLIB_IRQCHIP
help
Say Y here to support Phytium PCI GPIO controller on Px210 chipset.
An interrupt is generated when any of the inputs change state
(low to high or high to low).
This driver can be used for Phytium Px210.
config GPIO_RDC321X
tristate "RDC R-321x GPIO support"
select MFD_CORE

View File

@ -103,6 +103,10 @@ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o
obj-$(CONFIG_GPIO_PHYTIUM_CORE) += gpio-phytium-core.o
obj-$(CONFIG_GPIO_PHYTIUM_PCI) += gpio-phytium-pci.o
obj-$(CONFIG_GPIO_PHYTIUM_PLAT) += gpio-phytium-platform.o
obj-$(CONFIG_GPIO_PHYTIUM_SGPIO) += gpio-phytium-sgpio.o
obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o
obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o
obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o

View File

@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019-2023, Phytium Phytium Technology Co., Ltd.
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include "gpio-phytium-core.h"
static int get_pin_location(struct phytium_gpio *gpio, unsigned int offset,
struct pin_loc *pl)
{
int ret;
if (offset < gpio->ngpio[0]) {
pl->port = 0;
pl->offset = offset;
ret = 0;
} else if (offset < (gpio->ngpio[0] + gpio->ngpio[1])) {
pl->port = 1;
pl->offset = offset - gpio->ngpio[0];
ret = 0;
} else {
ret = -EINVAL;
}
return ret;
}
static void phytium_gpio_toggle_trigger(struct phytium_gpio *gpio,
unsigned int offset)
{
struct gpio_chip *gc;
u32 pol;
int val;
/* Only port A can provide interrupt source */
if (offset >= gpio->ngpio[0])
return;
gc = &gpio->gc;
pol = readl(gpio->regs + GPIO_INT_POLARITY);
/* Just read the current value right out of the data register */
val = gc->get(gc, offset);
if (val)
pol &= ~BIT(offset);
else
pol |= BIT(offset);
writel(pol, gpio->regs + GPIO_INT_POLARITY);
}
int phytium_gpio_get(struct gpio_chip *gc, unsigned int offset)
{
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct pin_loc loc;
void __iomem *dat;
if (get_pin_location(gpio, offset, &loc))
return -EINVAL;
dat = gpio->regs + GPIO_EXT_PORTA + (loc.port * GPIO_PORT_STRIDE);
return !!(readl(dat) & BIT(loc.offset));
}
EXPORT_SYMBOL_GPL(phytium_gpio_get);
void phytium_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct pin_loc loc;
void __iomem *dr;
unsigned long flags;
u32 mask;
if (get_pin_location(gpio, offset, &loc))
return;
dr = gpio->regs + GPIO_SWPORTA_DR + (loc.port * GPIO_PORT_STRIDE);
raw_spin_lock_irqsave(&gpio->lock, flags);
if (value)
mask = readl(dr) | BIT(loc.offset);
else
mask = readl(dr) & ~BIT(loc.offset);
writel(mask, dr);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
}
EXPORT_SYMBOL_GPL(phytium_gpio_set);
int phytium_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
{
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct pin_loc loc;
unsigned long flags;
void __iomem *ddr;
if (get_pin_location(gpio, offset, &loc))
return -EINVAL;
ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE);
raw_spin_lock_irqsave(&gpio->lock, flags);
writel(readl(ddr) & ~(BIT(loc.offset)), ddr);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(phytium_gpio_direction_input);
int phytium_gpio_direction_output(struct gpio_chip *gc, unsigned int offset,
int value)
{
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct pin_loc loc;
unsigned long flags;
void __iomem *ddr;
if (get_pin_location(gpio, offset, &loc))
return -EINVAL;
ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE);
raw_spin_lock_irqsave(&gpio->lock, flags);
writel(readl(ddr) | BIT(loc.offset), ddr);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
phytium_gpio_set(gc, offset, value);
return 0;
}
EXPORT_SYMBOL_GPL(phytium_gpio_direction_output);
void phytium_gpio_irq_ack(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
u32 val = BIT(irqd_to_hwirq(d));
raw_spin_lock(&gpio->lock);
writel(val, gpio->regs + GPIO_PORTA_EOI);
raw_spin_unlock(&gpio->lock);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_ack);
void phytium_gpio_irq_mask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
u32 val;
/* Only port A can provide interrupt source */
if (irqd_to_hwirq(d) >= gpio->ngpio[0])
return;
raw_spin_lock(&gpio->lock);
val = readl(gpio->regs + GPIO_INTMASK);
val |= BIT(irqd_to_hwirq(d));
writel(val, gpio->regs + GPIO_INTMASK);
raw_spin_unlock(&gpio->lock);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_mask);
void phytium_gpio_irq_unmask(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
u32 val;
/* Only port A can provide interrupt source */
if (irqd_to_hwirq(d) >= gpio->ngpio[0])
return;
raw_spin_lock(&gpio->lock);
val = readl(gpio->regs + GPIO_INTMASK);
val &= ~BIT(irqd_to_hwirq(d));
writel(val, gpio->regs + GPIO_INTMASK);
raw_spin_unlock(&gpio->lock);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_unmask);
int phytium_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
int hwirq = irqd_to_hwirq(d);
unsigned long flags, lvl, pol;
if (hwirq < 0 || hwirq >= gpio->ngpio[0])
return -EINVAL;
if ((flow_type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
(flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))) {
dev_err(gc->parent,
"trying to configure line %d for both level and edge detection, choose one!\n",
hwirq);
return -EINVAL;
}
raw_spin_lock_irqsave(&gpio->lock, flags);
lvl = readl(gpio->regs + GPIO_INTTYPE_LEVEL);
pol = readl(gpio->regs + GPIO_INT_POLARITY);
switch (flow_type) {
case IRQ_TYPE_EDGE_BOTH:
lvl |= BIT(hwirq);
phytium_gpio_toggle_trigger(gpio, hwirq);
irq_set_handler_locked(d, handle_edge_irq);
dev_dbg(gc->parent, "line %d: IRQ on both edges\n", hwirq);
break;
case IRQ_TYPE_EDGE_RISING:
lvl |= BIT(hwirq);
pol |= BIT(hwirq);
irq_set_handler_locked(d, handle_edge_irq);
dev_dbg(gc->parent, "line %d: IRQ on RISING edge\n", hwirq);
break;
case IRQ_TYPE_EDGE_FALLING:
lvl |= BIT(hwirq);
pol &= ~BIT(hwirq);
irq_set_handler_locked(d, handle_edge_irq);
dev_dbg(gc->parent, "line %d: IRQ on FALLING edge\n", hwirq);
break;
case IRQ_TYPE_LEVEL_HIGH:
lvl &= ~BIT(hwirq);
pol |= BIT(hwirq);
irq_set_handler_locked(d, handle_level_irq);
dev_dbg(gc->parent, "line %d: IRQ on HIGH level\n", hwirq);
break;
case IRQ_TYPE_LEVEL_LOW:
lvl &= ~BIT(hwirq);
pol &= ~BIT(hwirq);
irq_set_handler_locked(d, handle_level_irq);
dev_dbg(gc->parent, "line %d: IRQ on LOW level\n", hwirq);
break;
}
writel(lvl, gpio->regs + GPIO_INTTYPE_LEVEL);
if (flow_type != IRQ_TYPE_EDGE_BOTH)
writel(pol, gpio->regs + GPIO_INT_POLARITY);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_set_type);
void phytium_gpio_irq_enable(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
unsigned long flags;
u32 val;
/* Only port A can provide interrupt source */
if (irqd_to_hwirq(d) >= gpio->ngpio[0])
return;
raw_spin_lock_irqsave(&gpio->lock, flags);
val = readl(gpio->regs + GPIO_INTEN);
val |= BIT(irqd_to_hwirq(d));
writel(val, gpio->regs + GPIO_INTEN);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_enable);
void phytium_gpio_irq_disable(struct irq_data *d)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
unsigned long flags;
u32 val;
/* Only port A can provide interrupt source */
if (irqd_to_hwirq(d) >= gpio->ngpio[0])
return;
raw_spin_lock_irqsave(&gpio->lock, flags);
val = readl(gpio->regs + GPIO_INTEN);
val &= ~BIT(irqd_to_hwirq(d));
writel(val, gpio->regs + GPIO_INTEN);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_disable);
void phytium_gpio_irq_handler(struct irq_desc *desc)
{
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct irq_chip *irqchip = irq_desc_get_chip(desc);
unsigned long pending;
int offset;
chained_irq_enter(irqchip, desc);
pending = readl(gpio->regs + GPIO_INTSTATUS);
if (pending) {
for_each_set_bit(offset, &pending, gpio->ngpio[0]) {
int gpio_irq = irq_find_mapping(gc->irq.domain,
offset);
generic_handle_irq(gpio_irq);
if ((irq_get_trigger_type(gpio_irq) &
IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH)
phytium_gpio_toggle_trigger(gpio, offset);
}
}
chained_irq_exit(irqchip, desc);
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_handler);
int phytium_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
struct phytium_gpio *gpio = gpiochip_get_data(gc);
struct pin_loc loc;
void __iomem *ddr;
if (get_pin_location(gpio, offset, &loc))
return -EINVAL;
ddr = gpio->regs + GPIO_SWPORTA_DDR + (loc.port * GPIO_PORT_STRIDE);
return !(readl(ddr) & BIT(loc.offset));
}
EXPORT_SYMBOL_GPL(phytium_gpio_get_direction);
#if CONFIG_SMP
int
phytium_gpio_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force)
{
struct gpio_chip *chip_data = irq_data_get_irq_chip_data(d);
struct irq_chip *chip = irq_get_chip(chip_data->irq.num_parents);
struct irq_data *data = irq_get_irq_data(chip_data->irq.num_parents);
if (chip && chip->irq_set_affinity)
return chip->irq_set_affinity(data, mask_val, force);
return -EINVAL;
}
EXPORT_SYMBOL_GPL(phytium_gpio_irq_set_affinity);
#endif
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Phytium GPIO Controller core");

View File

@ -0,0 +1,87 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
*/
#ifndef _GPIO_PHYTIUM_H
#define _GPIO_PHYTIUM_H
#include <linux/gpio/driver.h>
#include <linux/spinlock.h>
#include "gpiolib.h"
#define GPIO_SWPORTA_DR 0x00 /* WR Port A Output Data Register */
#define GPIO_SWPORTA_DDR 0x04 /* WR Port A Data Direction Register */
#define GPIO_EXT_PORTA 0x08 /* RO Port A Input Data Register */
#define GPIO_SWPORTB_DR 0x0c /* WR Port B Output Data Register */
#define GPIO_SWPORTB_DDR 0x10 /* WR Port B Data Direction Register */
#define GPIO_EXT_PORTB 0x14 /* RO Port B Input Data Register */
#define GPIO_INTEN 0x18 /* WR Port A Interrput Enable Register */
#define GPIO_INTMASK 0x1c /* WR Port A Interrupt Mask Register */
#define GPIO_INTTYPE_LEVEL 0x20 /* WR Port A Interrupt Level Register */
#define GPIO_INT_POLARITY 0x24 /* WR Port A Interrupt Polarity Register */
#define GPIO_INTSTATUS 0x28 /* RO Port A Interrupt Status Register */
#define GPIO_RAW_INTSTATUS 0x2c /* RO Port A Raw Interrupt Status Register */
#define GPIO_LS_SYNC 0x30 /* WR Level-sensitive Synchronization Enable Register */
#define GPIO_DEBOUNCE 0x34 /* WR Debounce Enable Register */
#define GPIO_PORTA_EOI 0x38 /* WO Port A Clear Interrupt Register */
#define MAX_NPORTS 2
#define NGPIO_DEFAULT 8
#define NGPIO_MAX 32
#define GPIO_PORT_STRIDE (GPIO_EXT_PORTB - GPIO_EXT_PORTA)
struct pin_loc {
unsigned int port;
unsigned int offset;
};
#ifdef CONFIG_PM_SLEEP
struct phytium_gpio_ctx {
u32 swporta_dr;
u32 swporta_ddr;
u32 ext_porta;
u32 swportb_dr;
u32 swportb_ddr;
u32 ext_portb;
u32 inten;
u32 intmask;
u32 inttype_level;
u32 int_polarity;
u32 intstatus;
u32 raw_intstatus;
u32 ls_sync;
u32 debounce;
};
#endif
struct phytium_gpio {
raw_spinlock_t lock;
void __iomem *regs;
struct gpio_chip gc;
struct irq_chip irq_chip;
unsigned int ngpio[2];
int irq[32];
#ifdef CONFIG_PM_SLEEP
struct phytium_gpio_ctx ctx;
#endif
};
int phytium_gpio_get(struct gpio_chip *gc, unsigned int offset);
void phytium_gpio_set(struct gpio_chip *gc, unsigned int offset, int value);
int phytium_gpio_get_direction(struct gpio_chip *gc, unsigned int offset);
int phytium_gpio_direction_input(struct gpio_chip *gc, unsigned int offset);
int phytium_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value);
void phytium_gpio_irq_ack(struct irq_data *d);
void phytium_gpio_irq_mask(struct irq_data *d);
void phytium_gpio_irq_unmask(struct irq_data *d);
int phytium_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type);
void phytium_gpio_irq_enable(struct irq_data *d);
void phytium_gpio_irq_disable(struct irq_data *d);
void phytium_gpio_irq_handler(struct irq_desc *desc);
int phytium_gpio_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force);
#endif

View File

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
*/
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "gpio-phytium-core.h"
static int phytium_gpio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct device *dev = &pdev->dev;
struct phytium_gpio *gpio;
struct gpio_irq_chip *girq;
int err;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
err = pcim_enable_device(pdev);
if (err) {
dev_err(dev, "Failed to enable PCI device: err %d\n", err);
goto out;
}
err = pcim_iomap_regions(pdev, 1 << 0, pci_name(pdev));
if (err) {
dev_err(dev, "Failed to iomap PCI device: err %d\n", err);
goto out;
}
gpio->regs = pcim_iomap_table(pdev)[0];
if (!gpio->regs) {
dev_err(dev, "Cannot map PCI resource\n");
err = -ENOMEM;
goto out;
}
err = pci_enable_msi(pdev);
if (err < 0)
goto out;
gpio->irq[0] = pdev->irq;
if (gpio->irq < 0)
dev_warn(dev, "no irq is found.\n");
/* There is only one group of Pins at the moment. */
gpio->ngpio[0] = NGPIO_MAX;
/* irq_chip support */
gpio->irq_chip.name = dev_name(dev);
gpio->irq_chip.irq_ack = phytium_gpio_irq_ack;
gpio->irq_chip.irq_mask = phytium_gpio_irq_mask;
gpio->irq_chip.irq_unmask = phytium_gpio_irq_unmask;
gpio->irq_chip.irq_set_type = phytium_gpio_irq_set_type;
gpio->irq_chip.irq_enable = phytium_gpio_irq_enable;
gpio->irq_chip.irq_disable = phytium_gpio_irq_disable;
raw_spin_lock_init(&gpio->lock);
gpio->gc.base = -1;
gpio->gc.get_direction = phytium_gpio_get_direction;
gpio->gc.direction_input = phytium_gpio_direction_input;
gpio->gc.direction_output = phytium_gpio_direction_output;
gpio->gc.get = phytium_gpio_get;
gpio->gc.set = phytium_gpio_set;
gpio->gc.ngpio = gpio->ngpio[0] + gpio->ngpio[1];
gpio->gc.label = dev_name(dev);
gpio->gc.parent = dev;
gpio->gc.owner = THIS_MODULE;
girq = &gpio->gc.irq;
girq->handler = handle_bad_irq;
girq->default_type = IRQ_TYPE_NONE;
girq->num_parents = 1;
girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents,
sizeof(*girq->parents), GFP_KERNEL);
if (!girq->parents)
return -ENOMEM;
girq->parents[0] = gpio->irq[0];
girq->parent_handler = phytium_gpio_irq_handler;
girq->chip = &gpio->irq_chip;
err = devm_gpiochip_add_data(dev, &gpio->gc, gpio);
if (err)
goto out;
dev_info(dev, "Phytium PCI GPIO controller @%pa registered\n",
&gpio->regs);
pci_set_drvdata(pdev, gpio);
out:
return err;
}
static const struct pci_device_id phytium_gpio_pci_ids[] = {
{ PCI_DEVICE(0x1DB7, 0xDC31) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, phytium_gpio_pci_ids);
#ifdef CONFIG_PM_SLEEP
static int phytium_gpio_pci_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_gpio *gpio = pci_get_drvdata(pdev);
unsigned long flags;
raw_spin_lock_irqsave(&gpio->lock, flags);
gpio->ctx.swporta_dr = readl(gpio->regs + GPIO_SWPORTA_DR);
gpio->ctx.swporta_ddr = readl(gpio->regs + GPIO_SWPORTA_DDR);
gpio->ctx.ext_porta = readl(gpio->regs + GPIO_EXT_PORTA);
gpio->ctx.swportb_dr = readl(gpio->regs + GPIO_SWPORTB_DR);
gpio->ctx.swportb_ddr = readl(gpio->regs + GPIO_SWPORTB_DDR);
gpio->ctx.ext_portb = readl(gpio->regs + GPIO_EXT_PORTB);
gpio->ctx.inten = readl(gpio->regs + GPIO_INTEN);
gpio->ctx.intmask = readl(gpio->regs + GPIO_INTMASK);
gpio->ctx.inttype_level = readl(gpio->regs + GPIO_INTTYPE_LEVEL);
gpio->ctx.int_polarity = readl(gpio->regs + GPIO_INT_POLARITY);
gpio->ctx.debounce = readl(gpio->regs + GPIO_DEBOUNCE);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
static int phytium_gpio_pci_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_gpio *gpio = pci_get_drvdata(pdev);
unsigned long flags;
raw_spin_lock_irqsave(&gpio->lock, flags);
writel(gpio->ctx.swporta_dr, gpio->regs + GPIO_SWPORTA_DR);
writel(gpio->ctx.swporta_ddr, gpio->regs + GPIO_SWPORTA_DDR);
writel(gpio->ctx.ext_porta, gpio->regs + GPIO_EXT_PORTA);
writel(gpio->ctx.swportb_dr, gpio->regs + GPIO_SWPORTB_DR);
writel(gpio->ctx.swportb_ddr, gpio->regs + GPIO_SWPORTB_DDR);
writel(gpio->ctx.ext_portb, gpio->regs + GPIO_EXT_PORTB);
writel(gpio->ctx.inten, gpio->regs + GPIO_INTEN);
writel(gpio->ctx.intmask, gpio->regs + GPIO_INTMASK);
writel(gpio->ctx.inttype_level, gpio->regs + GPIO_INTTYPE_LEVEL);
writel(gpio->ctx.int_polarity, gpio->regs + GPIO_INT_POLARITY);
writel(gpio->ctx.debounce, gpio->regs + GPIO_DEBOUNCE);
writel(0xffffffff, gpio->regs + GPIO_PORTA_EOI);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(phytium_gpio_pci_pm_ops,
phytium_gpio_pci_suspend,
phytium_gpio_pci_resume);
static struct pci_driver phytium_gpio_pci_driver = {
.name = "gpio-phytium-pci",
.id_table = phytium_gpio_pci_ids,
.probe = phytium_gpio_pci_probe,
.driver = {
.pm = &phytium_gpio_pci_pm_ops,
},
};
module_pci_driver(phytium_gpio_pci_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Cheng Quan <chengquan@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium GPIO PCI Driver");

View File

@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Support functions for Phytium GPIO
*
* Copyright (c) 2019-2023, Phytium Phytium Technology Co., Ltd.
*
* Derived from drivers/gpio/gpio-pl061.c
* Copyright (C) 2008, 2009 Provigent Ltd.
*/
#include <linux/acpi.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include "gpio-phytium-core.h"
static const struct of_device_id phytium_gpio_of_match[] = {
{ .compatible = "phytium,gpio", },
{ }
};
MODULE_DEVICE_TABLE(of, phytium_gpio_of_match);
static const struct acpi_device_id phytium_gpio_acpi_match[] = {
{ "PHYT0001", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, phytium_gpio_acpi_match);
static int phytium_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct phytium_gpio *gpio;
struct gpio_irq_chip *girq;
struct fwnode_handle *fwnode;
int err, irq_count;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(gpio->regs))
return PTR_ERR(gpio->regs);
if (!device_get_child_node_count(dev))
return -ENODEV;
device_for_each_child_node(dev, fwnode) {
int idx;
if (fwnode_property_read_u32(fwnode, "reg", &idx) ||
idx >= MAX_NPORTS) {
dev_err(dev, "missing/invalid port index\n");
fwnode_handle_put(fwnode);
return -EINVAL;
}
if (fwnode_property_read_u32(fwnode, "nr-gpios",
&gpio->ngpio[idx])) {
dev_info(dev,
"failed to get number of gpios for Port%c\n",
idx ? 'B' : 'A');
gpio->ngpio[idx] = NGPIO_DEFAULT;
}
}
/* irq_chip support */
gpio->irq_chip.name = dev_name(dev);
gpio->irq_chip.irq_ack = phytium_gpio_irq_ack;
gpio->irq_chip.irq_mask = phytium_gpio_irq_mask;
gpio->irq_chip.irq_unmask = phytium_gpio_irq_unmask;
gpio->irq_chip.irq_set_type = phytium_gpio_irq_set_type;
gpio->irq_chip.irq_enable = phytium_gpio_irq_enable;
gpio->irq_chip.irq_disable = phytium_gpio_irq_disable;
#ifdef CONFIG_SMP
gpio->irq_chip.irq_set_affinity = phytium_gpio_irq_set_affinity;
#endif
raw_spin_lock_init(&gpio->lock);
gpio->gc.base = -1;
gpio->gc.get_direction = phytium_gpio_get_direction;
gpio->gc.direction_input = phytium_gpio_direction_input;
gpio->gc.direction_output = phytium_gpio_direction_output;
gpio->gc.get = phytium_gpio_get;
gpio->gc.set = phytium_gpio_set;
gpio->gc.ngpio = gpio->ngpio[0] + gpio->ngpio[1];
gpio->gc.label = dev_name(dev);
gpio->gc.parent = dev;
gpio->gc.owner = THIS_MODULE;
girq = &gpio->gc.irq;
girq->handler = handle_bad_irq;
girq->default_type = IRQ_TYPE_NONE;
for (irq_count = 0; irq_count < gpio->ngpio[0]; irq_count++) {
gpio->irq[irq_count] = -ENXIO;
gpio->irq[irq_count] = platform_get_irq(pdev, irq_count);
if (gpio->irq[irq_count] < 0) {
dev_warn(dev, "no irq is found.\n");
break;
}
};
girq->num_parents = irq_count;
girq->parents = gpio->irq;
girq->parent_handler = phytium_gpio_irq_handler;
girq->chip = &gpio->irq_chip;
err = devm_gpiochip_add_data(dev, &gpio->gc, gpio);
if (err)
return err;
platform_set_drvdata(pdev, gpio);
dev_info(dev, "Phytium GPIO controller @%pa registered\n",
&res->start);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int phytium_gpio_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct phytium_gpio *gpio = platform_get_drvdata(pdev);
unsigned long flags;
raw_spin_lock_irqsave(&gpio->lock, flags);
gpio->ctx.swporta_dr = readl(gpio->regs + GPIO_SWPORTA_DR);
gpio->ctx.swporta_ddr = readl(gpio->regs + GPIO_SWPORTA_DDR);
gpio->ctx.ext_porta = readl(gpio->regs + GPIO_EXT_PORTA);
gpio->ctx.swportb_dr = readl(gpio->regs + GPIO_SWPORTB_DR);
gpio->ctx.swportb_ddr = readl(gpio->regs + GPIO_SWPORTB_DDR);
gpio->ctx.ext_portb = readl(gpio->regs + GPIO_EXT_PORTB);
gpio->ctx.inten = readl(gpio->regs + GPIO_INTEN);
gpio->ctx.intmask = readl(gpio->regs + GPIO_INTMASK);
gpio->ctx.inttype_level = readl(gpio->regs + GPIO_INTTYPE_LEVEL);
gpio->ctx.int_polarity = readl(gpio->regs + GPIO_INT_POLARITY);
gpio->ctx.debounce = readl(gpio->regs + GPIO_DEBOUNCE);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
static int phytium_gpio_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct phytium_gpio *gpio = platform_get_drvdata(pdev);
unsigned long flags;
raw_spin_lock_irqsave(&gpio->lock, flags);
writel(gpio->ctx.swporta_dr, gpio->regs + GPIO_SWPORTA_DR);
writel(gpio->ctx.swporta_ddr, gpio->regs + GPIO_SWPORTA_DDR);
writel(gpio->ctx.ext_porta, gpio->regs + GPIO_EXT_PORTA);
writel(gpio->ctx.swportb_dr, gpio->regs + GPIO_SWPORTB_DR);
writel(gpio->ctx.swportb_ddr, gpio->regs + GPIO_SWPORTB_DDR);
writel(gpio->ctx.ext_portb, gpio->regs + GPIO_EXT_PORTB);
writel(gpio->ctx.inten, gpio->regs + GPIO_INTEN);
writel(gpio->ctx.intmask, gpio->regs + GPIO_INTMASK);
writel(gpio->ctx.inttype_level, gpio->regs + GPIO_INTTYPE_LEVEL);
writel(gpio->ctx.int_polarity, gpio->regs + GPIO_INT_POLARITY);
writel(gpio->ctx.debounce, gpio->regs + GPIO_DEBOUNCE);
writel(0xffffffff, gpio->regs + GPIO_PORTA_EOI);
raw_spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(phytium_gpio_pm_ops, phytium_gpio_suspend,
phytium_gpio_resume);
static struct platform_driver phytium_gpio_driver = {
.driver = {
.name = "gpio-phytium-platform",
.pm = &phytium_gpio_pm_ops,
.of_match_table = of_match_ptr(phytium_gpio_of_match),
.acpi_match_table = ACPI_PTR(phytium_gpio_acpi_match),
},
.probe = phytium_gpio_probe,
};
module_platform_driver(phytium_gpio_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Chen Baozi <chenbaozi@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium GPIO driver");

View File

@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Phytium SGPIO Driver
*
* Copyright (c) 2021-2023, Phytium Technology Co., Ltd.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/gpio/driver.h>
#include <linux/hashtable.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#define SGPIO_CTL0_REG 0x00
#define SGPIO_CTL0_REG_ENABLE BIT(0)
#define SGPIO_CTL0_REG_RX_DISABLE BIT(1)
#define SGPIO_CTL0_REG_L3_L0 GENMASK(11, 8)
#define SGPIO_CTL0_REG_CLK_DIV_NUM GENMASK(31, 12)
#define SGPIO_CTL1_REG 0x04
#define SGPIO_CTL1_REG_READY BIT(0)
#define SGPIO_CTL1_REG_W_UPDATA BIT(1)
#define SGPIO_CTL1_REG_OP_MODE BIT(2)
#define SGPIO_CTL1_REG_OP_STATE BIT(3)
#define SGPIO_CTL1_REG_BIT_NUM GENMASK(14, 8)
#define SGPIO_CTL1_REG_INTERVAL_TIMER GENMASK(31, 16)
#define SGPIO_SOFT_RESET_REG 0x08
#define SGPIO_SOFT_RESET_REG_MASK BIT(0)
#define SGPIO_IRQ_REG 0x0c
#define SGPIO_IRQ_REG_MASK BIT(0)
#define SGPIO_IRQ_M_REG 0x10
#define SGPIO_IRQ_M_REG_MASK BIT(0)
#define SGPIO_WDATA0_REG 0x14
#define SGPIO_WDATA_REG(x) (SGPIO_WDATA0_REG + (x) * 4)
#define SGPIO_RDATA0_REG 0x24
#define SGPIO_RDATA_REG(x) (SGPIO_RDATA0_REG + (x) * 4)
#define DEFAULT_L3_L0 0
#define GPIO_GROUP(x) ((x) >> 6)
#define GPIO_OFFSET(x) ((x) & GENMASK(5, 0))
#define GPIO_BIT(x) BIT(GPIO_OFFSET(x) >> 1)
struct phytium_sgpio {
struct gpio_chip gc;
void __iomem *regs;
unsigned int ngpios;
struct clk *pclk;
struct mutex lock;
struct completion completion;
};
static bool phytium_sgpio_is_input(unsigned int offset)
{
return !(offset % 2);
}
static int sgpio_set_value(struct gpio_chip *gc, unsigned int offset, int val)
{
struct phytium_sgpio *gpio = gpiochip_get_data(gc);
u32 reg;
int rc = 0;
if (phytium_sgpio_is_input(offset))
return -EINVAL;
reinit_completion(&gpio->completion);
/*
* Since this is an output, read the cached value from rdata,
* then update value.
*/
reg = readl(gpio->regs + SGPIO_RDATA_REG(GPIO_GROUP(offset)));
if (val)
reg |= GPIO_BIT(offset);
else
reg &= GPIO_BIT(offset);
writel(reg, gpio->regs + SGPIO_WDATA_REG(GPIO_GROUP(offset)));
/* Start transmission and wait for completion */
writel(readl(gpio->regs + SGPIO_CTL1_REG) | SGPIO_CTL1_REG_W_UPDATA,
gpio->regs + SGPIO_CTL1_REG);
if (!wait_for_completion_timeout(&gpio->completion, msecs_to_jiffies(1000)))
rc = -EINVAL;
return rc;
}
static int phytium_sgpio_direction_input(struct gpio_chip *gc, unsigned int offset)
{
return phytium_sgpio_is_input(offset) ? 0 : -EINVAL;
}
static int phytium_sgpio_direction_output(struct gpio_chip *gc, unsigned int offset, int val)
{
struct phytium_sgpio *gpio = gpiochip_get_data(gc);
int rc;
mutex_lock(&gpio->lock);
/*
* No special action is required for setting the direction; we'll
* error-out in sgpio_set_value if this isn't an output GPIO
*/
rc = sgpio_set_value(&gpio->gc, offset, val);
mutex_unlock(&gpio->lock);
return rc;
}
static int phytium_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
return !!phytium_sgpio_is_input(offset);
}
static int phytium_sgpio_get(struct gpio_chip *gc, unsigned int offset)
{
struct phytium_sgpio *gpio = gpiochip_get_data(gc);
int rc = 0;
u32 val, ctl0;
mutex_lock(&gpio->lock);
if (!phytium_sgpio_is_input(offset)) {
val = readl(gpio->regs + SGPIO_WDATA_REG(GPIO_GROUP(offset)));
rc = !!(val & GPIO_BIT(offset));
mutex_unlock(&gpio->lock);
return rc;
}
reinit_completion(&gpio->completion);
/* Enable Rx */
ctl0 = readl(gpio->regs + SGPIO_CTL0_REG);
writel(ctl0 & ~SGPIO_CTL0_REG_RX_DISABLE, gpio->regs + SGPIO_CTL0_REG);
/* Start reading transaction and wait for completion */
writel(readl(gpio->regs + SGPIO_CTL1_REG) | SGPIO_CTL1_REG_W_UPDATA,
gpio->regs + SGPIO_CTL1_REG);
if (!wait_for_completion_timeout(&gpio->completion, msecs_to_jiffies(1000))) {
rc = -EINVAL;
goto err;
}
val = readl(gpio->regs + SGPIO_RDATA_REG(GPIO_GROUP(offset)));
rc = !!(val & GPIO_BIT(offset));
err:
/* Disalbe Rx to hold the value */
writel(ctl0 | SGPIO_CTL0_REG_RX_DISABLE, gpio->regs + SGPIO_CTL0_REG);
mutex_unlock(&gpio->lock);
return rc;
}
static void phytium_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val)
{
struct phytium_sgpio *gpio = gpiochip_get_data(gc);
mutex_lock(&gpio->lock);
sgpio_set_value(gc, offset, val);
mutex_unlock(&gpio->lock);
}
static irqreturn_t phytium_sgpio_irq_handler(int irq, void *data)
{
struct phytium_sgpio *gpio = data;
if (!readl(gpio->regs + SGPIO_IRQ_REG))
return IRQ_NONE;
/* Clear the interrupt */
writel(0, gpio->regs + SGPIO_IRQ_REG);
/* Check if tx/rx has been done */
if (!(readl(gpio->regs + SGPIO_CTL1_REG) & SGPIO_CTL1_REG_OP_STATE))
complete(&gpio->completion);
return IRQ_HANDLED;
}
static int phytium_sgpio_probe(struct platform_device *pdev)
{
u32 pclk_freq, sclk_freq, clk_div;
struct phytium_sgpio *gpio;
struct resource *res;
struct device *dev = &pdev->dev;
int rc;
gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(gpio->regs))
return PTR_ERR(gpio->regs);
if (devm_request_irq(dev, platform_get_irq(pdev, 0),
phytium_sgpio_irq_handler,
IRQF_SHARED, dev_name(dev), gpio)) {
dev_err(dev, "failed to request IRQ\n");
return -ENOENT;
}
rc = fwnode_property_read_u32(dev_fwnode(dev), "ngpios", &gpio->ngpios);
if (rc < 0) {
dev_err(dev, "Could not read ngpios property\n");
return -EINVAL;
} else if (gpio->ngpios % 32) {
dev_err(&pdev->dev, "Number of GPIOs not multiple of 32: %d\n",
gpio->ngpios);
return -EINVAL;
}
rc = fwnode_property_read_u32(dev_fwnode(dev), "bus-frequency", &sclk_freq);
if (rc < 0) {
dev_err(dev, "Could not read bus-frequency property\n");
return -EINVAL;
}
gpio->pclk = devm_clk_get(dev, NULL);
if (IS_ERR(gpio->pclk)) {
dev_err(dev, "Could not get the APB clock property\n");
return PTR_ERR(gpio->pclk);
}
rc = clk_prepare_enable(gpio->pclk);
if (rc) {
dev_err(dev, "failed to enable pclk: %d\n", rc);
return rc;
}
pclk_freq = clk_get_rate(gpio->pclk);
/*
* From the datasheet:
* (pclk / 2) / (clk_div + 1) = sclk
*/
if (sclk_freq == 0) {
dev_err(dev, "SCLK should not be 0\n");
return -EINVAL;
}
clk_div = (pclk_freq / (sclk_freq * 2)) - 1;
if (clk_div > (1 << 20) - 1) {
dev_err(dev, "clk_div is overflow\n");
return -EINVAL;
}
writel(FIELD_PREP(SGPIO_CTL0_REG_CLK_DIV_NUM, clk_div) |
FIELD_PREP(SGPIO_CTL0_REG_L3_L0, DEFAULT_L3_L0) |
SGPIO_CTL0_REG_RX_DISABLE | SGPIO_CTL0_REG_ENABLE,
gpio->regs + SGPIO_CTL0_REG);
writel(FIELD_PREP(SGPIO_CTL1_REG_BIT_NUM, gpio->ngpios) |
SGPIO_CTL1_REG_READY, gpio->regs + SGPIO_CTL1_REG);
mutex_init(&gpio->lock);
init_completion(&gpio->completion);
platform_set_drvdata(pdev, gpio);
gpio->gc.parent = dev;
gpio->gc.base = -1;
gpio->gc.ngpio = gpio->ngpios * 2;
gpio->gc.label = dev_name(dev);
gpio->gc.direction_input = phytium_sgpio_direction_input;
gpio->gc.direction_output = phytium_sgpio_direction_output;
gpio->gc.get_direction = phytium_sgpio_get_direction;
gpio->gc.get = phytium_sgpio_get;
gpio->gc.set = phytium_sgpio_set;
return devm_gpiochip_add_data(dev, &gpio->gc, gpio);
}
static const struct of_device_id phytium_sgpio_of_match[] = {
{ .compatible = "phytium,sgpio", },
{ }
};
MODULE_DEVICE_TABLE(of, phytium_sgpio_of_match);
static struct platform_driver phytium_sgpio_driver = {
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = of_match_ptr(phytium_sgpio_of_match),
},
.probe = phytium_sgpio_probe,
};
module_platform_driver(phytium_sgpio_driver);
MODULE_AUTHOR("Chen Baozi <chenbaozi@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium SGPIO driver");
MODULE_LICENSE("GPL");