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