Files
2024-06-17 20:18:01 +08:00

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");