gpio-sama5d2-piobu.c (6147B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * SAMA5D2 PIOBU GPIO controller 4 * 5 * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries 6 * 7 * Author: Andrei Stefanescu <andrei.stefanescu@microchip.com> 8 * 9 */ 10#include <linux/bits.h> 11#include <linux/gpio/driver.h> 12#include <linux/init.h> 13#include <linux/kernel.h> 14#include <linux/mfd/syscon.h> 15#include <linux/module.h> 16#include <linux/of.h> 17#include <linux/platform_device.h> 18#include <linux/regmap.h> 19 20#define PIOBU_NUM 8 21#define PIOBU_REG_SIZE 4 22 23/* 24 * backup mode protection register for tamper detection 25 * normal mode protection register for tamper detection 26 * wakeup signal generation 27 */ 28#define PIOBU_BMPR 0x7C 29#define PIOBU_NMPR 0x80 30#define PIOBU_WKPR 0x90 31 32#define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */ 33 34#define PIOBU_DET_OFFSET 16 35 36/* In the datasheet this bit is called OUTPUT */ 37#define PIOBU_DIRECTION BIT(8) 38#define PIOBU_OUT BIT(8) 39#define PIOBU_IN 0 40 41#define PIOBU_SOD BIT(9) 42#define PIOBU_PDS BIT(10) 43 44#define PIOBU_HIGH BIT(9) 45#define PIOBU_LOW 0 46 47struct sama5d2_piobu { 48 struct gpio_chip chip; 49 struct regmap *regmap; 50}; 51 52/* 53 * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call 54 * 55 * Do not consider pin for tamper detection (normal and backup modes) 56 * Do not consider pin as tamper wakeup interrupt source 57 */ 58static int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin) 59{ 60 int ret; 61 struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, 62 chip); 63 unsigned int mask = BIT(PIOBU_DET_OFFSET + pin); 64 65 ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0); 66 if (ret) 67 return ret; 68 69 ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0); 70 if (ret) 71 return ret; 72 73 return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0); 74} 75 76/* 77 * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register 78 */ 79static int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin, 80 unsigned int mask, unsigned int value) 81{ 82 int reg; 83 struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, 84 chip); 85 86 reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; 87 88 return regmap_update_bits(piobu->regmap, reg, mask, value); 89} 90 91/* 92 * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU 93 * register 94 */ 95static int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin, 96 unsigned int mask) 97{ 98 struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, 99 chip); 100 unsigned int val, reg; 101 int ret; 102 103 reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; 104 ret = regmap_read(piobu->regmap, reg, &val); 105 if (ret < 0) 106 return ret; 107 108 return val & mask; 109} 110 111/* 112 * sama5d2_piobu_get_direction() - gpiochip get_direction 113 */ 114static int sama5d2_piobu_get_direction(struct gpio_chip *chip, 115 unsigned int pin) 116{ 117 int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION); 118 119 if (ret < 0) 120 return ret; 121 122 return (ret == PIOBU_IN) ? GPIO_LINE_DIRECTION_IN : 123 GPIO_LINE_DIRECTION_OUT; 124} 125 126/* 127 * sama5d2_piobu_direction_input() - gpiochip direction_input 128 */ 129static int sama5d2_piobu_direction_input(struct gpio_chip *chip, 130 unsigned int pin) 131{ 132 return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, PIOBU_IN); 133} 134 135/* 136 * sama5d2_piobu_direction_output() - gpiochip direction_output 137 */ 138static int sama5d2_piobu_direction_output(struct gpio_chip *chip, 139 unsigned int pin, int value) 140{ 141 unsigned int val = PIOBU_OUT; 142 143 if (value) 144 val |= PIOBU_HIGH; 145 146 return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION | PIOBU_SOD, 147 val); 148} 149 150/* 151 * sama5d2_piobu_get() - gpiochip get 152 */ 153static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin) 154{ 155 /* if pin is input, read value from PDS else read from SOD */ 156 int ret = sama5d2_piobu_get_direction(chip, pin); 157 158 if (ret == GPIO_LINE_DIRECTION_IN) 159 ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS); 160 else if (ret == GPIO_LINE_DIRECTION_OUT) 161 ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD); 162 163 if (ret < 0) 164 return ret; 165 166 return !!ret; 167} 168 169/* 170 * sama5d2_piobu_set() - gpiochip set 171 */ 172static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin, 173 int value) 174{ 175 if (!value) 176 value = PIOBU_LOW; 177 else 178 value = PIOBU_HIGH; 179 180 sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value); 181} 182 183static int sama5d2_piobu_probe(struct platform_device *pdev) 184{ 185 struct sama5d2_piobu *piobu; 186 int ret, i; 187 188 piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL); 189 if (!piobu) 190 return -ENOMEM; 191 192 platform_set_drvdata(pdev, piobu); 193 piobu->chip.label = pdev->name; 194 piobu->chip.parent = &pdev->dev; 195 piobu->chip.owner = THIS_MODULE, 196 piobu->chip.get_direction = sama5d2_piobu_get_direction, 197 piobu->chip.direction_input = sama5d2_piobu_direction_input, 198 piobu->chip.direction_output = sama5d2_piobu_direction_output, 199 piobu->chip.get = sama5d2_piobu_get, 200 piobu->chip.set = sama5d2_piobu_set, 201 piobu->chip.base = -1, 202 piobu->chip.ngpio = PIOBU_NUM, 203 piobu->chip.can_sleep = 0, 204 205 piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node); 206 if (IS_ERR(piobu->regmap)) { 207 dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n", 208 PTR_ERR(piobu->regmap)); 209 return PTR_ERR(piobu->regmap); 210 } 211 212 ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu); 213 if (ret) { 214 dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret); 215 return ret; 216 } 217 218 for (i = 0; i < PIOBU_NUM; ++i) { 219 ret = sama5d2_piobu_setup_pin(&piobu->chip, i); 220 if (ret) { 221 dev_err(&pdev->dev, "Failed to setup pin: %d %d\n", 222 i, ret); 223 return ret; 224 } 225 } 226 227 return 0; 228} 229 230static const struct of_device_id sama5d2_piobu_ids[] = { 231 { .compatible = "atmel,sama5d2-secumod" }, 232 {}, 233}; 234MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids); 235 236static struct platform_driver sama5d2_piobu_driver = { 237 .driver = { 238 .name = "sama5d2-piobu", 239 .of_match_table = of_match_ptr(sama5d2_piobu_ids) 240 }, 241 .probe = sama5d2_piobu_probe, 242}; 243 244module_platform_driver(sama5d2_piobu_driver); 245 246MODULE_LICENSE("GPL v2"); 247MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver"); 248MODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>");