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>
This commit is contained in:
parent
969970ccbe
commit
6c79971d36
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -406,6 +410,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
|
||||
|
@ -1390,6 +1415,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
|
||||
|
|
|
@ -102,6 +102,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
|
||||
|
|
|
@ -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");
|
|
@ -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
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
Loading…
Reference in New Issue