forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
6.0 KiB
249 lines
6.0 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* SAMA5D2 PIOBU GPIO controller |
|
* |
|
* Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries |
|
* |
|
* Author: Andrei Stefanescu <[email protected]> |
|
* |
|
*/ |
|
#include <linux/bits.h> |
|
#include <linux/gpio/driver.h> |
|
#include <linux/init.h> |
|
#include <linux/kernel.h> |
|
#include <linux/mfd/syscon.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/regmap.h> |
|
|
|
#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.of_node = pdev->dev.of_node; |
|
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 <[email protected]>");
|
|
|