mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-28 14:07:12 +08:00
407 lines
11 KiB
C
407 lines
11 KiB
C
/*
|
|
* Loongson-3A/3B/7A GPIO Support
|
|
*
|
|
* Copyright (c) 2018 Juxin Gao <gaojuxin@loongson.cn>
|
|
* Copyright (c) 2019 Weihui Wang <wangweihui@loongson.cn>
|
|
*
|
|
* 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>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/bitops.h>
|
|
#include <asm/types.h>
|
|
|
|
#define GPIO_OEN(x) (x->base + x->conf_offset)
|
|
#define GPIO_OUT(x) (x->base + x->out_offset)
|
|
#define GPIO_IN(x) (x->base + x->in_offset)
|
|
#define GPIO_INT(x) (x->base + x->int_offset)
|
|
|
|
#define GPIO_OEN_BYTE(x, gpio) (x->base + x->conf_offset + gpio)
|
|
#define GPIO_OUT_BYTE(x, gpio) (x->base + x->out_offset + gpio)
|
|
#define GPIO_IN_BYTE(x, gpio) (x->base + x->in_offset + gpio)
|
|
#define GPIO_INT_BYTE(x, gpio) (x->base + x->int_offset + 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 int_offset;
|
|
int in_start_bit;
|
|
u16 *gsi_idx_map;
|
|
u16 mapsize;
|
|
bool support_irq;
|
|
bool bytewise;
|
|
};
|
|
|
|
/*
|
|
* GPIO primitives.
|
|
*/
|
|
static int loongson_gpio_request(struct gpio_chip *chip, unsigned pin)
|
|
{
|
|
if (pin >= chip->ngpio)
|
|
return -EINVAL;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
__set_direction(struct loongson_gpio_chip *lgpio, unsigned pin, int input)
|
|
{
|
|
u64 temp;
|
|
u8 value;
|
|
|
|
//"loongson,loongson3-gpio" "LOON0007"
|
|
if (!lgpio->bytewise) {
|
|
temp = readq(GPIO_OEN(lgpio));
|
|
if (input)
|
|
temp |= 1ULL << pin;
|
|
else
|
|
temp &= ~(1ULL << pin);
|
|
writeq(temp, GPIO_OEN(lgpio));
|
|
return ;
|
|
}
|
|
// "loongson,ls7a-gpio" "LOON0002"
|
|
else {
|
|
if (input)
|
|
value = 1;
|
|
else
|
|
value = 0;
|
|
writeb(value, GPIO_OEN_BYTE(lgpio, pin));
|
|
return ;
|
|
}
|
|
}
|
|
|
|
static void __set_level(struct loongson_gpio_chip *lgpio, unsigned pin, int high)
|
|
{
|
|
u64 temp;
|
|
u8 value;
|
|
|
|
/* If GPIO controller is on 3A,then... */
|
|
//"loongson,loongson3-gpio" "LOON0007"
|
|
if (!lgpio->bytewise) {
|
|
temp = readq(GPIO_OUT(lgpio));
|
|
if (high)
|
|
temp |= 1ULL << pin;
|
|
else
|
|
temp &= ~(1ULL << pin);
|
|
writeq(temp, GPIO_OUT(lgpio));
|
|
return;
|
|
}
|
|
// "loongson,ls7a-gpio" "LOON0002"
|
|
else {
|
|
if (high)
|
|
value = 1;
|
|
else
|
|
value = 0;
|
|
writeb(value, GPIO_OUT_BYTE(lgpio, pin));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned pin)
|
|
{
|
|
unsigned long flags;
|
|
struct loongson_gpio_chip *lgpio =
|
|
container_of(chip, struct loongson_gpio_chip, chip);
|
|
|
|
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 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 */
|
|
//"loongson,loongson3-gpio" "LOON0007"
|
|
if (!lgpio->bytewise) {
|
|
temp = readq(GPIO_IN(lgpio));
|
|
return ((temp & (1ULL << (pin + lgpio->in_start_bit))) != 0);
|
|
}
|
|
// "loongson,ls7a-gpio" "LOON0002"
|
|
else {
|
|
value = readb(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];
|
|
|
|
if (lgpio->support_irq) {
|
|
/* 2k1000或2k500的gpio中断有些是分组共享的,但每一组共享中断中只能有一个gpio用于中断,
|
|
如果需要使用多于一个gpio用于中断,硬件设计时要注意,把用于中断的gpio分到不同的组,
|
|
如何分组,请参考数据手册。
|
|
注意2k500的gpio分组中断,分组中最高位的gpio是不能用于中断的 */
|
|
if (!lgpio->bytewise) {
|
|
u64 temp = readb(GPIO_INT(lgpio));
|
|
writeq(temp | (0x1ull << offset), GPIO_INT(lgpio));
|
|
}
|
|
// 存在建议按字节读写的GPIO,比如2p500
|
|
else {
|
|
writeb(1, GPIO_INT_BYTE(lgpio, 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)
|
|
{
|
|
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;
|
|
|
|
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)) {
|
|
|
|
if (lgpio->support_irq) {
|
|
lgpio->chip.to_irq = loongson_gpio_to_irq;
|
|
if (!lgpio->bytewise) {
|
|
writeq(0x00, GPIO_INT(lgpio));
|
|
}
|
|
else {
|
|
int gpio;
|
|
for (gpio = 0; gpio < lgpio->chip.ngpio; gpio++)
|
|
writeb(0x00, GPIO_INT_BYTE(lgpio, gpio));
|
|
}
|
|
} else {
|
|
lgpio->chip.to_irq = NULL;
|
|
}
|
|
}
|
|
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_u32(np, "int_offset", (u32 *)&lgpio->int_offset);
|
|
lgpio->bytewise = of_property_read_bool(np, "bytewise");
|
|
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 = 1;
|
|
else
|
|
lgpio->support_irq = 0;
|
|
}
|
|
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);
|
|
device_property_read_u32(dev, "int_offset", (u32 *)&lgpio->int_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 = 1;
|
|
else
|
|
lgpio->support_irq = 0;
|
|
lgpio->bytewise = true;
|
|
}
|
|
lgpio->chip.label = kstrdup(pdev->name, GFP_KERNEL);
|
|
}
|
|
|
|
static int loongson_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
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;
|
|
|
|
lgpio = kzalloc(sizeof(struct loongson_gpio_chip), GFP_KERNEL);
|
|
if (!lgpio)
|
|
return -ENOMEM;
|
|
|
|
if (np){
|
|
of_loongson_gpio_get_props(np,lgpio);
|
|
} else if (ACPI_COMPANION(&pdev->dev)) {
|
|
acpi_loongson_gpio_get_props(pdev,lgpio);
|
|
} else {
|
|
return -ENODEV;
|
|
}
|
|
|
|
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 0;
|
|
out:
|
|
pr_err("%s: %s: missing mandatory property\n", __func__, np->name);
|
|
return ret;
|
|
}
|
|
|
|
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)
|
|
{
|
|
return platform_driver_register(&ls_gpio_driver);
|
|
}
|
|
subsys_initcall(loongson_gpio_setup);
|
|
|
|
static void __exit loongson_gpio_driver(void)
|
|
{
|
|
platform_driver_unregister(&ls_gpio_driver);
|
|
}
|
|
module_exit(loongson_gpio_driver);
|
|
MODULE_AUTHOR("Loongson Technology Corporation Limited");
|
|
MODULE_DESCRIPTION("LOONGSON GPIO");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:loongson_gpio");
|