162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Loongson GPIO Support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/init.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/spinlock.h>
1262306a36Sopenharmony_ci#include <linux/err.h>
1362306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/bitops.h>
1662306a36Sopenharmony_ci#include <asm/types.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cienum loongson_gpio_mode {
1962306a36Sopenharmony_ci	BIT_CTRL_MODE,
2062306a36Sopenharmony_ci	BYTE_CTRL_MODE,
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct loongson_gpio_chip_data {
2462306a36Sopenharmony_ci	const char		*label;
2562306a36Sopenharmony_ci	enum loongson_gpio_mode	mode;
2662306a36Sopenharmony_ci	unsigned int		conf_offset;
2762306a36Sopenharmony_ci	unsigned int		out_offset;
2862306a36Sopenharmony_ci	unsigned int		in_offset;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct loongson_gpio_chip {
3262306a36Sopenharmony_ci	struct gpio_chip	chip;
3362306a36Sopenharmony_ci	struct fwnode_handle	*fwnode;
3462306a36Sopenharmony_ci	spinlock_t		lock;
3562306a36Sopenharmony_ci	void __iomem		*reg_base;
3662306a36Sopenharmony_ci	const struct loongson_gpio_chip_data *chip_data;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic inline struct loongson_gpio_chip *to_loongson_gpio_chip(struct gpio_chip *chip)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	return container_of(chip, struct loongson_gpio_chip, chip);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic inline void loongson_commit_direction(struct loongson_gpio_chip *lgpio, unsigned int pin,
4562306a36Sopenharmony_ci					     int input)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	u8 bval = input ? 1 : 0;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	writeb(bval, lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic void loongson_commit_level(struct loongson_gpio_chip *lgpio, unsigned int pin, int high)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	u8 bval = high ? 1 : 0;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	writeb(bval, lgpio->reg_base + lgpio->chip_data->out_offset + pin);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned int pin)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	unsigned long flags;
6262306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	spin_lock_irqsave(&lgpio->lock, flags);
6562306a36Sopenharmony_ci	loongson_commit_direction(lgpio, pin, 1);
6662306a36Sopenharmony_ci	spin_unlock_irqrestore(&lgpio->lock, flags);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	return 0;
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic int loongson_gpio_direction_output(struct gpio_chip *chip, unsigned int pin, int value)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	unsigned long flags;
7462306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	spin_lock_irqsave(&lgpio->lock, flags);
7762306a36Sopenharmony_ci	loongson_commit_level(lgpio, pin, value);
7862306a36Sopenharmony_ci	loongson_commit_direction(lgpio, pin, 0);
7962306a36Sopenharmony_ci	spin_unlock_irqrestore(&lgpio->lock, flags);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return 0;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int loongson_gpio_get(struct gpio_chip *chip, unsigned int pin)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	u8  bval;
8762306a36Sopenharmony_ci	int val;
8862306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	bval = readb(lgpio->reg_base + lgpio->chip_data->in_offset + pin);
9162306a36Sopenharmony_ci	val = bval & 1;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return val;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic int loongson_gpio_get_direction(struct gpio_chip *chip, unsigned int pin)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	u8  bval;
9962306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	bval = readb(lgpio->reg_base + lgpio->chip_data->conf_offset + pin);
10262306a36Sopenharmony_ci	if (bval & 1)
10362306a36Sopenharmony_ci		return GPIO_LINE_DIRECTION_IN;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return GPIO_LINE_DIRECTION_OUT;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	unsigned long flags;
11162306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	spin_lock_irqsave(&lgpio->lock, flags);
11462306a36Sopenharmony_ci	loongson_commit_level(lgpio, pin, value);
11562306a36Sopenharmony_ci	spin_unlock_irqrestore(&lgpio->lock, flags);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct platform_device *pdev = to_platform_device(chip->parent);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return platform_get_irq(pdev, offset);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio,
12662306a36Sopenharmony_ci			      struct device_node *np, void __iomem *reg_base)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	int ret;
12962306a36Sopenharmony_ci	u32 ngpios;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	lgpio->reg_base = reg_base;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (lgpio->chip_data->mode == BIT_CTRL_MODE) {
13462306a36Sopenharmony_ci		ret = bgpio_init(&lgpio->chip, dev, 8,
13562306a36Sopenharmony_ci				lgpio->reg_base + lgpio->chip_data->in_offset,
13662306a36Sopenharmony_ci				lgpio->reg_base + lgpio->chip_data->out_offset,
13762306a36Sopenharmony_ci				NULL, NULL,
13862306a36Sopenharmony_ci				lgpio->reg_base + lgpio->chip_data->conf_offset,
13962306a36Sopenharmony_ci				0);
14062306a36Sopenharmony_ci		if (ret) {
14162306a36Sopenharmony_ci			dev_err(dev, "unable to init generic GPIO\n");
14262306a36Sopenharmony_ci			return ret;
14362306a36Sopenharmony_ci		}
14462306a36Sopenharmony_ci	} else {
14562306a36Sopenharmony_ci		lgpio->chip.direction_input = loongson_gpio_direction_input;
14662306a36Sopenharmony_ci		lgpio->chip.get = loongson_gpio_get;
14762306a36Sopenharmony_ci		lgpio->chip.get_direction = loongson_gpio_get_direction;
14862306a36Sopenharmony_ci		lgpio->chip.direction_output = loongson_gpio_direction_output;
14962306a36Sopenharmony_ci		lgpio->chip.set = loongson_gpio_set;
15062306a36Sopenharmony_ci		lgpio->chip.parent = dev;
15162306a36Sopenharmony_ci		spin_lock_init(&lgpio->lock);
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	device_property_read_u32(dev, "ngpios", &ngpios);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	lgpio->chip.can_sleep = 0;
15762306a36Sopenharmony_ci	lgpio->chip.ngpio = ngpios;
15862306a36Sopenharmony_ci	lgpio->chip.label = lgpio->chip_data->label;
15962306a36Sopenharmony_ci	lgpio->chip.to_irq = loongson_gpio_to_irq;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return devm_gpiochip_add_data(dev, &lgpio->chip, lgpio);
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic int loongson_gpio_probe(struct platform_device *pdev)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	void __iomem *reg_base;
16762306a36Sopenharmony_ci	struct loongson_gpio_chip *lgpio;
16862306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
16962306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	lgpio = devm_kzalloc(dev, sizeof(*lgpio), GFP_KERNEL);
17262306a36Sopenharmony_ci	if (!lgpio)
17362306a36Sopenharmony_ci		return -ENOMEM;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	lgpio->chip_data = device_get_match_data(dev);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	reg_base = devm_platform_ioremap_resource(pdev, 0);
17862306a36Sopenharmony_ci	if (IS_ERR(reg_base))
17962306a36Sopenharmony_ci		return PTR_ERR(reg_base);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return loongson_gpio_init(dev, lgpio, np, reg_base);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = {
18562306a36Sopenharmony_ci	.label = "ls2k_gpio",
18662306a36Sopenharmony_ci	.mode = BIT_CTRL_MODE,
18762306a36Sopenharmony_ci	.conf_offset = 0x0,
18862306a36Sopenharmony_ci	.in_offset = 0x20,
18962306a36Sopenharmony_ci	.out_offset = 0x10,
19062306a36Sopenharmony_ci};
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
19362306a36Sopenharmony_ci	.label = "ls7a_gpio",
19462306a36Sopenharmony_ci	.mode = BYTE_CTRL_MODE,
19562306a36Sopenharmony_ci	.conf_offset = 0x800,
19662306a36Sopenharmony_ci	.in_offset = 0xa00,
19762306a36Sopenharmony_ci	.out_offset = 0x900,
19862306a36Sopenharmony_ci};
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic const struct of_device_id loongson_gpio_of_match[] = {
20162306a36Sopenharmony_ci	{
20262306a36Sopenharmony_ci		.compatible = "loongson,ls2k-gpio",
20362306a36Sopenharmony_ci		.data = &loongson_gpio_ls2k_data,
20462306a36Sopenharmony_ci	},
20562306a36Sopenharmony_ci	{
20662306a36Sopenharmony_ci		.compatible = "loongson,ls7a-gpio",
20762306a36Sopenharmony_ci		.data = &loongson_gpio_ls7a_data,
20862306a36Sopenharmony_ci	},
20962306a36Sopenharmony_ci	{}
21062306a36Sopenharmony_ci};
21162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, loongson_gpio_of_match);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic const struct acpi_device_id loongson_gpio_acpi_match[] = {
21462306a36Sopenharmony_ci	{
21562306a36Sopenharmony_ci		.id = "LOON0002",
21662306a36Sopenharmony_ci		.driver_data = (kernel_ulong_t)&loongson_gpio_ls7a_data,
21762306a36Sopenharmony_ci	},
21862306a36Sopenharmony_ci	{}
21962306a36Sopenharmony_ci};
22062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic struct platform_driver loongson_gpio_driver = {
22362306a36Sopenharmony_ci	.driver = {
22462306a36Sopenharmony_ci		.name = "loongson-gpio",
22562306a36Sopenharmony_ci		.of_match_table = loongson_gpio_of_match,
22662306a36Sopenharmony_ci		.acpi_match_table = loongson_gpio_acpi_match,
22762306a36Sopenharmony_ci	},
22862306a36Sopenharmony_ci	.probe = loongson_gpio_probe,
22962306a36Sopenharmony_ci};
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic int __init loongson_gpio_setup(void)
23262306a36Sopenharmony_ci{
23362306a36Sopenharmony_ci	return platform_driver_register(&loongson_gpio_driver);
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_cipostcore_initcall(loongson_gpio_setup);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ciMODULE_DESCRIPTION("Loongson gpio driver");
23862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
239