提交 44b9464a 编写于 作者: J Juxin Gao 提交者: openeuler-sync-bot

gpio: loongson: Add 3A/3B/3C/7A gpio dirver support

LoongArch inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I6BWFP

--------------------------------

Change-Id: Ib1adc61f5279bba8020f26acc32a4de4dee95df5
Signed-off-by: NJuxin Gao <gaojuxin@loongson.cn>
(cherry picked from commit 3c16558b)
上级 eb54873c
......@@ -370,7 +370,8 @@ config GPIO_LOGICVC
config GPIO_LOONGSON
bool "Loongson-2/3 GPIO support"
depends on CPU_LOONGSON2EF || CPU_LOONGSON64
depends on CPU_LOONGSON2EF || CPU_LOONGSON64 || LOONGARCH
default m
help
driver for GPIO functionality on Loongson-2F/3A/3B processors.
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Loongson-2F/3A/3B GPIO Support
* Loongson-3A/3B/3C/7A GPIO Support
*
* Copyright (c) 2008 Richard Liu, STMicroelectronics <richard.liu@st.com>
* Copyright (c) 2008-2010 Arnaud Patard <apatard@mandriva.com>
* Copyright (c) 2013 Hongbing Hu <huhb@lemote.com>
* Copyright (c) 2014 Huacai Chen <chenhc@lemote.com>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/acpi.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
......@@ -17,119 +17,382 @@
#include <linux/platform_device.h>
#include <linux/bitops.h>
#include <asm/types.h>
#include <loongson.h>
#define STLS2F_N_GPIO 4
#define STLS3A_N_GPIO 16
/* ============== Data structrues =============== */
#ifdef CONFIG_CPU_LOONGSON64
#define LOONGSON_N_GPIO STLS3A_N_GPIO
#else
#define LOONGSON_N_GPIO STLS2F_N_GPIO
#endif
/* gpio data */
struct platform_gpio_data {
u32 gpio_conf;
u32 gpio_out;
u32 gpio_in;
u32 in_start_bit;
u32 support_irq;
char *label;
int gpio_base;
int ngpio;
};
/*
* Offset into the register where we read lines, we write them from offset 0.
* This offset is the only thing that stand between us and using
* GPIO_GENERIC.
*/
#define LOONGSON_GPIO_IN_OFFSET 16
#define GPIO_IO_CONF(x) (x->base + x->conf_offset)
#define GPIO_OUT(x) (x->base + x->out_offset)
#define GPIO_IN(x) (x->base + x->in_offset)
static DEFINE_SPINLOCK(gpio_lock);
#define LS7A_GPIO_OEN_BYTE(x, gpio) (x->base + x->conf_offset + gpio)
#define LS7A_GPIO_OUT_BYTE(x, gpio) (x->base + x->out_offset + gpio)
#define LS7A_GPIO_IN_BYTE(x, gpio) (x->base + x->in_offset + gpio)
static int loongson_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
struct loongson_gpio_chip {
struct gpio_chip chip;
spinlock_t lock;
void __iomem *base;
int conf_offset;
int out_offset;
int in_offset;
int in_start_bit;
u16 *gsi_idx_map;
u16 mapsize;
bool support_irq;
};
/*
* GPIO primitives.
*/
static int loongson_gpio_request(struct gpio_chip *chip, unsigned pin)
{
u32 val;
if (pin >= chip->ngpio)
return -EINVAL;
else
return 0;
}
spin_lock(&gpio_lock);
val = LOONGSON_GPIODATA;
spin_unlock(&gpio_lock);
static inline void
__set_direction(struct loongson_gpio_chip *lgpio, unsigned pin, int input)
{
u64 temp;
u8 value;
return !!(val & BIT(gpio + LOONGSON_GPIO_IN_OFFSET));
if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") ||
!strncmp(lgpio->chip.label, "LOON0007", 8)) {
temp = readq(GPIO_IO_CONF(lgpio));
if (input)
temp |= 1ULL << pin;
else
temp &= ~(1ULL << pin);
writeq(temp, GPIO_IO_CONF(lgpio));
return ;
}
if (!strcmp(lgpio->chip.label,"loongson,ls7a-gpio") ||
!strncmp(lgpio->chip.label, "LOON0002", 8)){
if (input)
value = 1;
else
value = 0;
writeb(value, LS7A_GPIO_OEN_BYTE(lgpio, pin));
return ;
}
}
static void loongson_gpio_set_value(struct gpio_chip *chip,
unsigned gpio, int value)
static void __set_level(struct loongson_gpio_chip *lgpio, unsigned pin, int high)
{
u32 val;
u64 temp;
u8 value;
spin_lock(&gpio_lock);
val = LOONGSON_GPIODATA;
if (value)
val |= BIT(gpio);
/* If GPIO controller is on 3A,then... */
if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") ||
!strncmp(lgpio->chip.label, "LOON0007", 8)) {
temp = readq(GPIO_OUT(lgpio));
if (high)
temp |= 1ULL << pin;
else
val &= ~BIT(gpio);
LOONGSON_GPIODATA = val;
spin_unlock(&gpio_lock);
temp &= ~(1ULL << pin);
writeq(temp, GPIO_OUT(lgpio));
return;
}
if (!strcmp(lgpio->chip.label,"loongson,ls7a-gpio") ||
!strncmp(lgpio->chip.label,"LOON0002", 8)){
if (high)
value = 1;
else
value = 0;
writeb(value, LS7A_GPIO_OUT_BYTE(lgpio, pin));
return;
}
}
static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned pin)
{
u32 temp;
unsigned long flags;
struct loongson_gpio_chip *lgpio =
container_of(chip, struct loongson_gpio_chip, chip);
spin_lock(&gpio_lock);
temp = LOONGSON_GPIOIE;
temp |= BIT(gpio);
LOONGSON_GPIOIE = temp;
spin_unlock(&gpio_lock);
spin_lock_irqsave(&lgpio->lock, flags);
__set_direction(lgpio, pin, 1);
spin_unlock_irqrestore(&lgpio->lock, flags);
return 0;
}
static int loongson_gpio_direction_output(struct gpio_chip *chip,
unsigned gpio, int level)
unsigned pin, int value)
{
struct loongson_gpio_chip *lgpio =
container_of(chip, struct loongson_gpio_chip, chip);
unsigned long flags;
spin_lock_irqsave(&lgpio->lock, flags);
__set_level(lgpio, pin, value);
__set_direction(lgpio, pin, 0);
spin_unlock_irqrestore(&lgpio->lock, flags);
return 0;
}
static int loongson_gpio_get(struct gpio_chip *chip, unsigned pin)
{
struct loongson_gpio_chip *lgpio =
container_of(chip, struct loongson_gpio_chip, chip);
u64 temp;
u8 value;
/* GPIO controller in 3A is different for 7A */
if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") ||
!strncmp(lgpio->chip.label, "LOON0007", 8)) {
temp = readq(GPIO_IN(lgpio));
return ((temp & (1ULL << (pin + lgpio->in_start_bit))) != 0);
}
if (!strcmp(lgpio->chip.label,"loongson,ls7a-gpio") ||
!strncmp(lgpio->chip.label, "LOON0002", 8)){
value = readb(LS7A_GPIO_IN_BYTE(lgpio, pin));
return (value & 1);
}
return -ENXIO;
}
static void loongson_gpio_set(struct gpio_chip *chip, unsigned pin, int value)
{
struct loongson_gpio_chip *lgpio =
container_of(chip, struct loongson_gpio_chip, chip);
unsigned long flags;
spin_lock_irqsave(&lgpio->lock, flags);
__set_level(lgpio, pin, value);
spin_unlock_irqrestore(&lgpio->lock, flags);
}
static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
{
struct platform_device *pdev =
container_of(chip->parent, struct platform_device, dev);
struct loongson_gpio_chip *lgpio =
container_of(chip, struct loongson_gpio_chip, chip);
if (offset >= chip->ngpio)
return -EINVAL;
if ((lgpio->gsi_idx_map != NULL) && (offset < lgpio->mapsize))
offset = lgpio->gsi_idx_map[offset];
return platform_get_irq(pdev, offset);
}
static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio, struct device_node *np,
void __iomem *base)
{
u32 temp;
lgpio->chip.request = loongson_gpio_request;
lgpio->chip.direction_input = loongson_gpio_direction_input;
lgpio->chip.get = loongson_gpio_get;
lgpio->chip.direction_output = loongson_gpio_direction_output;
lgpio->chip.set = loongson_gpio_set;
lgpio->chip.can_sleep = 0;
lgpio->chip.of_node = np;
lgpio->chip.parent = dev;
spin_lock_init(&lgpio->lock);
lgpio->base = (void __iomem *)base;
loongson_gpio_set_value(chip, gpio, level);
spin_lock(&gpio_lock);
temp = LOONGSON_GPIOIE;
temp &= ~BIT(gpio);
LOONGSON_GPIOIE = temp;
spin_unlock(&gpio_lock);
if (!strcmp(lgpio->chip.label, "loongson,ls7a-gpio") ||
!strncmp(lgpio->chip.label, "LOON0002", 8) ||
!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") ||
!strncmp(lgpio->chip.label, "LOON0007", 8)) {
lgpio->chip.to_irq = loongson_gpio_to_irq;
}
gpiochip_add(&lgpio->chip);
return 0;
}
static void of_loongson_gpio_get_props(struct device_node *np,
struct loongson_gpio_chip *lgpio)
{
const char *name;
of_property_read_u32(np, "ngpios", (u32 *)&lgpio->chip.ngpio);
of_property_read_u32(np, "gpio_base", (u32 *)&lgpio->chip.base);
of_property_read_u32(np, "conf_offset", (u32 *)&lgpio->conf_offset);
of_property_read_u32(np, "out_offset", (u32 *)&lgpio->out_offset);
of_property_read_u32(np, "in_offset", (u32 *)&lgpio->in_offset);
of_property_read_string(np, "compatible", &name);
if (!strcmp(name, "loongson,loongson3-gpio")) {
of_property_read_u32(np, "in_start_bit",
(u32 *)&lgpio->in_start_bit);
if (of_property_read_bool(np, "support_irq"))
lgpio->support_irq = true;
}
lgpio->chip.label = kstrdup(name, GFP_KERNEL);
}
static void acpi_loongson_gpio_get_props(struct platform_device *pdev,
struct loongson_gpio_chip *lgpio)
{
struct device *dev = &pdev->dev;
int rval;
device_property_read_u32(dev, "ngpios", (u32 *)&lgpio->chip.ngpio);
device_property_read_u32(dev, "gpio_base", (u32 *)&lgpio->chip.base);
device_property_read_u32(dev, "conf_offset", (u32 *)&lgpio->conf_offset);
device_property_read_u32(dev, "out_offset", (u32 *)&lgpio->out_offset);
device_property_read_u32(dev, "in_offset", (u32 *)&lgpio->in_offset);
rval = device_property_read_u16_array(dev, "gsi_idx_map", NULL, 0);
if (rval > 0) {
lgpio->gsi_idx_map =
kmalloc_array(rval, sizeof(*lgpio->gsi_idx_map),
GFP_KERNEL);
if (unlikely(!lgpio->gsi_idx_map)) {
dev_err(dev, "Alloc gsi_idx_map fail!\n");
} else {
lgpio->mapsize = rval;
device_property_read_u16_array(dev, "gsi_idx_map",
lgpio->gsi_idx_map, lgpio->mapsize);
}
}
if (!strcmp(pdev->name, "LOON0007")) {
device_property_read_u32(dev, "in_start_bit",
(u32 *)&lgpio->in_start_bit);
if (device_property_read_bool(dev, "support_irq"))
lgpio->support_irq = true;
}
lgpio->chip.label = kstrdup(pdev->name, GFP_KERNEL);
}
static void platform_loongson_gpio_get_props(struct platform_device *pdev,
struct loongson_gpio_chip *lgpio)
{
struct platform_gpio_data *gpio_data =
(struct platform_gpio_data *)pdev->dev.platform_data;
lgpio->chip.ngpio = gpio_data->ngpio;
lgpio->chip.base = gpio_data->gpio_base;
lgpio->conf_offset = gpio_data->gpio_conf;
lgpio->out_offset = gpio_data->gpio_out;
lgpio->in_offset = gpio_data->gpio_in;
if (!strcmp(gpio_data->label, "loongson,loongson3-gpio")) {
lgpio->in_start_bit = gpio_data->in_start_bit;
lgpio->support_irq = gpio_data->support_irq;
}
lgpio->chip.label = kstrdup(gpio_data->label, GFP_KERNEL);
}
static int loongson_gpio_probe(struct platform_device *pdev)
{
struct gpio_chip *gc;
struct resource *iores;
void __iomem *base;
struct loongson_gpio_chip *lgpio;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int ret = 0;
gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
if (!gc)
lgpio = kzalloc(sizeof(struct loongson_gpio_chip), GFP_KERNEL);
if (!lgpio)
return -ENOMEM;
gc->label = "loongson-gpio-chip";
gc->base = 0;
gc->ngpio = LOONGSON_N_GPIO;
gc->get = loongson_gpio_get_value;
gc->set = loongson_gpio_set_value;
gc->direction_input = loongson_gpio_direction_input;
gc->direction_output = loongson_gpio_direction_output;
if (np){
of_loongson_gpio_get_props(np,lgpio);
} else if (ACPI_COMPANION(&pdev->dev)) {
acpi_loongson_gpio_get_props(pdev,lgpio);
} else {
platform_loongson_gpio_get_props(pdev,lgpio);
}
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!iores) {
ret = -ENODEV;
goto out;
}
if (!request_mem_region(iores->start, resource_size(iores),
pdev->name)) {
ret = -EBUSY;
goto out;
}
base = ioremap(iores->start, resource_size(iores));
if (!base) {
ret = -ENOMEM;
goto out;
}
platform_set_drvdata(pdev, lgpio);
loongson_gpio_init(dev,lgpio, np, base);
return gpiochip_add_data(gc, NULL);
return 0;
out:
pr_err("%s: %s: missing mandatory property\n", __func__, np->name);
return ret;
}
static struct platform_driver loongson_gpio_driver = {
static int loongson_gpio_remove(struct platform_device *pdev)
{
struct loongson_gpio_chip *lgpio = platform_get_drvdata(pdev);
struct resource *mem;
platform_set_drvdata(pdev, NULL);
gpiochip_remove(&lgpio->chip);
iounmap(lgpio->base);
kfree(lgpio->gsi_idx_map);
kfree(lgpio);
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(mem->start, resource_size(mem));
return 0;
}
static const struct of_device_id loongson_gpio_dt_ids[] = {
{ .compatible = "loongson,loongson3-gpio"},
{ .compatible = "loongson,ls7a-gpio"},
{}
};
MODULE_DEVICE_TABLE(of, loongson_gpio_dt_ids);
static const struct acpi_device_id loongson_gpio_acpi_match[] = {
{"LOON0002"},
{"LOON0007"},
{}
};
MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match);
static struct platform_driver ls_gpio_driver = {
.driver = {
.name = "loongson-gpio",
.owner = THIS_MODULE,
.of_match_table = loongson_gpio_dt_ids,
.acpi_match_table = ACPI_PTR(loongson_gpio_acpi_match),
},
.probe = loongson_gpio_probe,
.remove = loongson_gpio_remove,
};
static int __init loongson_gpio_setup(void)
{
struct platform_device *pdev;
int ret;
ret = platform_driver_register(&loongson_gpio_driver);
if (ret) {
pr_err("error registering loongson GPIO driver\n");
return ret;
}
return platform_driver_register(&ls_gpio_driver);
}
subsys_initcall(loongson_gpio_setup);
pdev = platform_device_register_simple("loongson-gpio", -1, NULL, 0);
return PTR_ERR_OR_ZERO(pdev);
static void __exit loongson_gpio_driver(void)
{
platform_driver_unregister(&ls_gpio_driver);
}
postcore_initcall(loongson_gpio_setup);
module_exit(loongson_gpio_driver);
MODULE_AUTHOR("Loongson Technology Corporation Limited");
MODULE_DESCRIPTION("LOONGSON GPIO");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:loongson_gpio");
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册