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