1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (C) 2019 Bootlin 4 * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> 5 */ 6 7 #include <linux/err.h> 8 #include <linux/gpio/driver.h> 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <linux/of_address.h> 12 #include <linux/platform_device.h> 13 #include <linux/regmap.h> 14 #include <linux/mfd/syscon.h> 15 16 #define LOGICVC_CTRL_REG 0x40 17 #define LOGICVC_CTRL_GPIO_SHIFT 11 18 #define LOGICVC_CTRL_GPIO_BITS 5 19 20 #define LOGICVC_POWER_CTRL_REG 0x78 21 #define LOGICVC_POWER_CTRL_GPIO_SHIFT 0 22 #define LOGICVC_POWER_CTRL_GPIO_BITS 4 23 24 struct logicvc_gpio { 25 struct gpio_chip chip; 26 struct regmap *regmap; 27 }; 28 29 static void logicvc_gpio_offset(struct logicvc_gpio *logicvc, unsigned offset, 30 unsigned int *reg, unsigned int *bit) 31 { 32 if (offset >= LOGICVC_CTRL_GPIO_BITS) { 33 *reg = LOGICVC_POWER_CTRL_REG; 34 35 /* To the (virtual) power ctrl offset. */ 36 offset -= LOGICVC_CTRL_GPIO_BITS; 37 /* To the actual bit offset in reg. */ 38 offset += LOGICVC_POWER_CTRL_GPIO_SHIFT; 39 } else { 40 *reg = LOGICVC_CTRL_REG; 41 42 /* To the actual bit offset in reg. */ 43 offset += LOGICVC_CTRL_GPIO_SHIFT; 44 } 45 46 *bit = BIT(offset); 47 } 48 49 static int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset) 50 { 51 struct logicvc_gpio *logicvc = gpiochip_get_data(chip); 52 unsigned int reg, bit, value; 53 int ret; 54 55 logicvc_gpio_offset(logicvc, offset, ®, &bit); 56 57 ret = regmap_read(logicvc->regmap, reg, &value); 58 if (ret) 59 return ret; 60 61 return !!(value & bit); 62 } 63 64 static void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 65 { 66 struct logicvc_gpio *logicvc = gpiochip_get_data(chip); 67 unsigned int reg, bit; 68 69 logicvc_gpio_offset(logicvc, offset, ®, &bit); 70 71 regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0); 72 } 73 74 static int logicvc_gpio_direction_output(struct gpio_chip *chip, 75 unsigned offset, int value) 76 { 77 /* Pins are always configured as output, so just set the value. */ 78 logicvc_gpio_set(chip, offset, value); 79 80 return 0; 81 } 82 83 static struct regmap_config logicvc_gpio_regmap_config = { 84 .reg_bits = 32, 85 .val_bits = 32, 86 .reg_stride = 4, 87 .name = "logicvc-gpio", 88 }; 89 90 static int logicvc_gpio_probe(struct platform_device *pdev) 91 { 92 struct device *dev = &pdev->dev; 93 struct device_node *of_node = dev->of_node; 94 struct logicvc_gpio *logicvc; 95 int ret; 96 97 logicvc = devm_kzalloc(dev, sizeof(*logicvc), GFP_KERNEL); 98 if (!logicvc) 99 return -ENOMEM; 100 101 /* Try to get regmap from parent first. */ 102 logicvc->regmap = syscon_node_to_regmap(of_node->parent); 103 104 /* Grab our own regmap if that fails. */ 105 if (IS_ERR(logicvc->regmap)) { 106 struct resource res; 107 void __iomem *base; 108 109 ret = of_address_to_resource(of_node, 0, &res); 110 if (ret) { 111 dev_err(dev, "Failed to get resource from address\n"); 112 return ret; 113 } 114 115 base = devm_ioremap_resource(dev, &res); 116 if (IS_ERR(base)) 117 return PTR_ERR(base); 118 119 logicvc_gpio_regmap_config.max_register = resource_size(&res) - 120 logicvc_gpio_regmap_config.reg_stride; 121 122 logicvc->regmap = 123 devm_regmap_init_mmio(dev, base, 124 &logicvc_gpio_regmap_config); 125 if (IS_ERR(logicvc->regmap)) { 126 dev_err(dev, "Failed to create regmap for I/O\n"); 127 return PTR_ERR(logicvc->regmap); 128 } 129 } 130 131 logicvc->chip.parent = dev; 132 logicvc->chip.owner = THIS_MODULE; 133 logicvc->chip.label = dev_name(dev); 134 logicvc->chip.base = -1; 135 logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS + 136 LOGICVC_POWER_CTRL_GPIO_BITS; 137 logicvc->chip.get = logicvc_gpio_get; 138 logicvc->chip.set = logicvc_gpio_set; 139 logicvc->chip.direction_output = logicvc_gpio_direction_output; 140 141 return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc); 142 } 143 144 static const struct of_device_id logicivc_gpio_of_table[] = { 145 { 146 .compatible = "xylon,logicvc-3.02.a-gpio", 147 }, 148 { } 149 }; 150 151 MODULE_DEVICE_TABLE(of, logicivc_gpio_of_table); 152 153 static struct platform_driver logicvc_gpio_driver = { 154 .driver = { 155 .name = "gpio-logicvc", 156 .of_match_table = logicivc_gpio_of_table, 157 }, 158 .probe = logicvc_gpio_probe, 159 }; 160 161 module_platform_driver(logicvc_gpio_driver); 162 163 MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); 164 MODULE_DESCRIPTION("Xylon LogiCVC GPIO driver"); 165 MODULE_LICENSE("GPL"); 166