// SPDX-License-Identifier: GPL-2.0 /* * SAMA5D2 PIOBU GPIO controller * * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries * * Author: Andrei Stefanescu * */ #include #include #include #include #include #include #include #include #include #define PIOBU_NUM 8 #define PIOBU_REG_SIZE 4 /* * backup mode protection register for tamper detection * normal mode protection register for tamper detection * wakeup signal generation */ #define PIOBU_BMPR 0x7C #define PIOBU_NMPR 0x80 #define PIOBU_WKPR 0x90 #define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */ #define PIOBU_DET_OFFSET 16 /* In the datasheet this bit is called OUTPUT */ #define PIOBU_DIRECTION BIT(8) #define PIOBU_OUT BIT(8) #define PIOBU_IN 0 #define PIOBU_SOD BIT(9) #define PIOBU_PDS BIT(10) #define PIOBU_HIGH BIT(9) #define PIOBU_LOW 0 struct sama5d2_piobu { struct gpio_chip chip; struct regmap *regmap; }; /* * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call * * Do not consider pin for tamper detection (normal and backup modes) * Do not consider pin as tamper wakeup interrupt source */ static int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin) { int ret; struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, chip); unsigned int mask = BIT(PIOBU_DET_OFFSET + pin); ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0); if (ret) return ret; ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0); if (ret) return ret; return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0); } /* * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register */ static int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin, unsigned int mask, unsigned int value) { int reg; struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, chip); reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; return regmap_update_bits(piobu->regmap, reg, mask, value); } /* * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU * register */ static int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin, unsigned int mask) { struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, chip); unsigned int val, reg; int ret; reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; ret = regmap_read(piobu->regmap, reg, &val); if (ret < 0) return ret; return val & mask; } /* * sama5d2_piobu_get_direction() - gpiochip get_direction */ static int sama5d2_piobu_get_direction(struct gpio_chip *chip, unsigned int pin) { int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION); if (ret < 0) return ret; return (ret == PIOBU_IN) ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; } /* * sama5d2_piobu_direction_input() - gpiochip direction_input */ static int sama5d2_piobu_direction_input(struct gpio_chip *chip, unsigned int pin) { return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, PIOBU_IN); } /* * sama5d2_piobu_direction_output() - gpiochip direction_output */ static int sama5d2_piobu_direction_output(struct gpio_chip *chip, unsigned int pin, int value) { unsigned int val = PIOBU_OUT; if (value) val |= PIOBU_HIGH; return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION | PIOBU_SOD, val); } /* * sama5d2_piobu_get() - gpiochip get */ static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin) { /* if pin is input, read value from PDS else read from SOD */ int ret = sama5d2_piobu_get_direction(chip, pin); if (ret == GPIO_LINE_DIRECTION_IN) ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS); else if (ret == GPIO_LINE_DIRECTION_OUT) ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD); if (ret < 0) return ret; return !!ret; } /* * sama5d2_piobu_set() - gpiochip set */ static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin, int value) { if (!value) value = PIOBU_LOW; else value = PIOBU_HIGH; sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value); } static int sama5d2_piobu_probe(struct platform_device *pdev) { struct sama5d2_piobu *piobu; int ret, i; piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL); if (!piobu) return -ENOMEM; platform_set_drvdata(pdev, piobu); piobu->chip.label = pdev->name; piobu->chip.parent = &pdev->dev; piobu->chip.owner = THIS_MODULE, piobu->chip.get_direction = sama5d2_piobu_get_direction, piobu->chip.direction_input = sama5d2_piobu_direction_input, piobu->chip.direction_output = sama5d2_piobu_direction_output, piobu->chip.get = sama5d2_piobu_get, piobu->chip.set = sama5d2_piobu_set, piobu->chip.base = -1, piobu->chip.ngpio = PIOBU_NUM, piobu->chip.can_sleep = 0, piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node); if (IS_ERR(piobu->regmap)) { dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n", PTR_ERR(piobu->regmap)); return PTR_ERR(piobu->regmap); } ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu); if (ret) { dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret); return ret; } for (i = 0; i < PIOBU_NUM; ++i) { ret = sama5d2_piobu_setup_pin(&piobu->chip, i); if (ret) { dev_err(&pdev->dev, "Failed to setup pin: %d %d\n", i, ret); return ret; } } return 0; } static const struct of_device_id sama5d2_piobu_ids[] = { { .compatible = "atmel,sama5d2-secumod" }, {}, }; MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids); static struct platform_driver sama5d2_piobu_driver = { .driver = { .name = "sama5d2-piobu", .of_match_table = of_match_ptr(sama5d2_piobu_ids) }, .probe = sama5d2_piobu_probe, }; module_platform_driver(sama5d2_piobu_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver"); MODULE_AUTHOR("Andrei Stefanescu ");