18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Synopsys CREG (Control REGisters) GPIO driver 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright (C) 2018 Synopsys 68c2ecf20Sopenharmony_ci// Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com> 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 98c2ecf20Sopenharmony_ci#include <linux/io.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#define MAX_GPIO 32 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistruct creg_layout { 168c2ecf20Sopenharmony_ci u8 ngpio; 178c2ecf20Sopenharmony_ci u8 shift[MAX_GPIO]; 188c2ecf20Sopenharmony_ci u8 on[MAX_GPIO]; 198c2ecf20Sopenharmony_ci u8 off[MAX_GPIO]; 208c2ecf20Sopenharmony_ci u8 bit_per_gpio[MAX_GPIO]; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct creg_gpio { 248c2ecf20Sopenharmony_ci struct gpio_chip gc; 258c2ecf20Sopenharmony_ci void __iomem *regs; 268c2ecf20Sopenharmony_ci spinlock_t lock; 278c2ecf20Sopenharmony_ci const struct creg_layout *layout; 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic void creg_gpio_set(struct gpio_chip *gc, unsigned int offset, int val) 318c2ecf20Sopenharmony_ci{ 328c2ecf20Sopenharmony_ci struct creg_gpio *hcg = gpiochip_get_data(gc); 338c2ecf20Sopenharmony_ci const struct creg_layout *layout = hcg->layout; 348c2ecf20Sopenharmony_ci u32 reg, reg_shift, value; 358c2ecf20Sopenharmony_ci unsigned long flags; 368c2ecf20Sopenharmony_ci int i; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci value = val ? hcg->layout->on[offset] : hcg->layout->off[offset]; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci reg_shift = layout->shift[offset]; 418c2ecf20Sopenharmony_ci for (i = 0; i < offset; i++) 428c2ecf20Sopenharmony_ci reg_shift += layout->bit_per_gpio[i] + layout->shift[i]; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci spin_lock_irqsave(&hcg->lock, flags); 458c2ecf20Sopenharmony_ci reg = readl(hcg->regs); 468c2ecf20Sopenharmony_ci reg &= ~(GENMASK(layout->bit_per_gpio[i] - 1, 0) << reg_shift); 478c2ecf20Sopenharmony_ci reg |= (value << reg_shift); 488c2ecf20Sopenharmony_ci writel(reg, hcg->regs); 498c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&hcg->lock, flags); 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int creg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci creg_gpio_set(gc, offset, val); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return 0; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int creg_gpio_validate_pg(struct device *dev, struct creg_gpio *hcg, 608c2ecf20Sopenharmony_ci int i) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci const struct creg_layout *layout = hcg->layout; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci if (layout->bit_per_gpio[i] < 1 || layout->bit_per_gpio[i] > 8) 658c2ecf20Sopenharmony_ci return -EINVAL; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* Check that on value fits its placeholder */ 688c2ecf20Sopenharmony_ci if (GENMASK(31, layout->bit_per_gpio[i]) & layout->on[i]) 698c2ecf20Sopenharmony_ci return -EINVAL; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* Check that off value fits its placeholder */ 728c2ecf20Sopenharmony_ci if (GENMASK(31, layout->bit_per_gpio[i]) & layout->off[i]) 738c2ecf20Sopenharmony_ci return -EINVAL; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (layout->on[i] == layout->off[i]) 768c2ecf20Sopenharmony_ci return -EINVAL; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic int creg_gpio_validate(struct device *dev, struct creg_gpio *hcg, 828c2ecf20Sopenharmony_ci u32 ngpios) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci u32 reg_len = 0; 858c2ecf20Sopenharmony_ci int i; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (hcg->layout->ngpio < 1 || hcg->layout->ngpio > MAX_GPIO) 888c2ecf20Sopenharmony_ci return -EINVAL; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (ngpios < 1 || ngpios > hcg->layout->ngpio) { 918c2ecf20Sopenharmony_ci dev_err(dev, "ngpios must be in [1:%u]\n", hcg->layout->ngpio); 928c2ecf20Sopenharmony_ci return -EINVAL; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci for (i = 0; i < hcg->layout->ngpio; i++) { 968c2ecf20Sopenharmony_ci if (creg_gpio_validate_pg(dev, hcg, i)) 978c2ecf20Sopenharmony_ci return -EINVAL; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci reg_len += hcg->layout->shift[i] + hcg->layout->bit_per_gpio[i]; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Check that we fit in 32 bit register */ 1038c2ecf20Sopenharmony_ci if (reg_len > 32) 1048c2ecf20Sopenharmony_ci return -EINVAL; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci return 0; 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic const struct creg_layout hsdk_cs_ctl = { 1108c2ecf20Sopenharmony_ci .ngpio = 10, 1118c2ecf20Sopenharmony_ci .shift = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 1128c2ecf20Sopenharmony_ci .off = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, 1138c2ecf20Sopenharmony_ci .on = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, 1148c2ecf20Sopenharmony_ci .bit_per_gpio = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 } 1158c2ecf20Sopenharmony_ci}; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic const struct creg_layout axs10x_flsh_cs_ctl = { 1188c2ecf20Sopenharmony_ci .ngpio = 1, 1198c2ecf20Sopenharmony_ci .shift = { 0 }, 1208c2ecf20Sopenharmony_ci .off = { 1 }, 1218c2ecf20Sopenharmony_ci .on = { 3 }, 1228c2ecf20Sopenharmony_ci .bit_per_gpio = { 2 } 1238c2ecf20Sopenharmony_ci}; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic const struct of_device_id creg_gpio_ids[] = { 1268c2ecf20Sopenharmony_ci { 1278c2ecf20Sopenharmony_ci .compatible = "snps,creg-gpio-axs10x", 1288c2ecf20Sopenharmony_ci .data = &axs10x_flsh_cs_ctl 1298c2ecf20Sopenharmony_ci }, { 1308c2ecf20Sopenharmony_ci .compatible = "snps,creg-gpio-hsdk", 1318c2ecf20Sopenharmony_ci .data = &hsdk_cs_ctl 1328c2ecf20Sopenharmony_ci }, { /* sentinel */ } 1338c2ecf20Sopenharmony_ci}; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int creg_gpio_probe(struct platform_device *pdev) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci const struct of_device_id *match; 1388c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1398c2ecf20Sopenharmony_ci struct creg_gpio *hcg; 1408c2ecf20Sopenharmony_ci u32 ngpios; 1418c2ecf20Sopenharmony_ci int ret; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci hcg = devm_kzalloc(dev, sizeof(struct creg_gpio), GFP_KERNEL); 1448c2ecf20Sopenharmony_ci if (!hcg) 1458c2ecf20Sopenharmony_ci return -ENOMEM; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci hcg->regs = devm_platform_ioremap_resource(pdev, 0); 1488c2ecf20Sopenharmony_ci if (IS_ERR(hcg->regs)) 1498c2ecf20Sopenharmony_ci return PTR_ERR(hcg->regs); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci match = of_match_node(creg_gpio_ids, pdev->dev.of_node); 1528c2ecf20Sopenharmony_ci hcg->layout = match->data; 1538c2ecf20Sopenharmony_ci if (!hcg->layout) 1548c2ecf20Sopenharmony_ci return -EINVAL; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci ret = of_property_read_u32(dev->of_node, "ngpios", &ngpios); 1578c2ecf20Sopenharmony_ci if (ret) 1588c2ecf20Sopenharmony_ci return ret; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci ret = creg_gpio_validate(dev, hcg, ngpios); 1618c2ecf20Sopenharmony_ci if (ret) 1628c2ecf20Sopenharmony_ci return ret; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci spin_lock_init(&hcg->lock); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci hcg->gc.label = dev_name(dev); 1678c2ecf20Sopenharmony_ci hcg->gc.base = -1; 1688c2ecf20Sopenharmony_ci hcg->gc.ngpio = ngpios; 1698c2ecf20Sopenharmony_ci hcg->gc.set = creg_gpio_set; 1708c2ecf20Sopenharmony_ci hcg->gc.direction_output = creg_gpio_dir_out; 1718c2ecf20Sopenharmony_ci hcg->gc.of_node = dev->of_node; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ret = devm_gpiochip_add_data(dev, &hcg->gc, hcg); 1748c2ecf20Sopenharmony_ci if (ret) 1758c2ecf20Sopenharmony_ci return ret; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci dev_info(dev, "GPIO controller with %d gpios probed\n", ngpios); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic struct platform_driver creg_gpio_snps_driver = { 1838c2ecf20Sopenharmony_ci .driver = { 1848c2ecf20Sopenharmony_ci .name = "snps-creg-gpio", 1858c2ecf20Sopenharmony_ci .of_match_table = creg_gpio_ids, 1868c2ecf20Sopenharmony_ci }, 1878c2ecf20Sopenharmony_ci .probe = creg_gpio_probe, 1888c2ecf20Sopenharmony_ci}; 1898c2ecf20Sopenharmony_cibuiltin_platform_driver(creg_gpio_snps_driver); 190