|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Congatec Board Controller GPIO driver | 
|  | * | 
|  | * Copyright (C) 2024 Bootlin | 
|  | * Author: Thomas Richard <thomas.richard@bootlin.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/gpio/driver.h> | 
|  | #include <linux/mfd/cgbc.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/platform_device.h> | 
|  |  | 
|  | #define CGBC_GPIO_NGPIO	14 | 
|  |  | 
|  | #define CGBC_GPIO_CMD_GET	0x64 | 
|  | #define CGBC_GPIO_CMD_SET	0x65 | 
|  | #define CGBC_GPIO_CMD_DIR_GET	0x66 | 
|  | #define CGBC_GPIO_CMD_DIR_SET	0x67 | 
|  |  | 
|  | struct cgbc_gpio_data { | 
|  | struct gpio_chip	chip; | 
|  | struct cgbc_device_data	*cgbc; | 
|  | struct mutex lock; | 
|  | }; | 
|  |  | 
|  | static int cgbc_gpio_cmd(struct cgbc_device_data *cgbc, | 
|  | u8 cmd0, u8 cmd1, u8 cmd2, u8 *value) | 
|  | { | 
|  | u8 cmd[3] = {cmd0, cmd1, cmd2}; | 
|  |  | 
|  | return cgbc_command(cgbc, cmd, sizeof(cmd), value, 1, NULL); | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_get(struct gpio_chip *chip, unsigned int offset) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  | struct cgbc_device_data *cgbc = gpio->cgbc; | 
|  | int ret; | 
|  | u8 val; | 
|  |  | 
|  | scoped_guard(mutex, &gpio->lock) | 
|  | ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val); | 
|  |  | 
|  | offset %= 8; | 
|  |  | 
|  | if (ret) | 
|  | return ret; | 
|  | else | 
|  | return (int)(val & (u8)BIT(offset)); | 
|  | } | 
|  |  | 
|  | static int __cgbc_gpio_set(struct gpio_chip *chip, unsigned int offset, | 
|  | int value) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  | struct cgbc_device_data *cgbc = gpio->cgbc; | 
|  | u8 val; | 
|  | int ret; | 
|  |  | 
|  | ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_GET, (offset > 7) ? 1 : 0, 0, &val); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (value) | 
|  | val |= BIT(offset % 8); | 
|  | else | 
|  | val &= ~(BIT(offset % 8)); | 
|  |  | 
|  | return cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_SET, (offset > 7) ? 1 : 0, val, &val); | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  |  | 
|  | guard(mutex)(&gpio->lock); | 
|  |  | 
|  | return __cgbc_gpio_set(chip, offset, value); | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_direction_set(struct gpio_chip *chip, | 
|  | unsigned int offset, int direction) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  | struct cgbc_device_data *cgbc = gpio->cgbc; | 
|  | int ret; | 
|  | u8 val; | 
|  |  | 
|  | ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val); | 
|  | if (ret) | 
|  | goto end; | 
|  |  | 
|  | if (direction == GPIO_LINE_DIRECTION_IN) | 
|  | val &= ~(BIT(offset % 8)); | 
|  | else | 
|  | val |= BIT(offset % 8); | 
|  |  | 
|  | ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_SET, (offset > 7) ? 1 : 0, val, &val); | 
|  |  | 
|  | end: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_direction_input(struct gpio_chip *chip, | 
|  | unsigned int offset) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  |  | 
|  | guard(mutex)(&gpio->lock); | 
|  | return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_IN); | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_direction_output(struct gpio_chip *chip, | 
|  | unsigned int offset, int value) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  | int ret; | 
|  |  | 
|  | guard(mutex)(&gpio->lock); | 
|  |  | 
|  | ret = __cgbc_gpio_set(chip, offset, value); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return cgbc_gpio_direction_set(chip, offset, GPIO_LINE_DIRECTION_OUT); | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) | 
|  | { | 
|  | struct cgbc_gpio_data *gpio = gpiochip_get_data(chip); | 
|  | struct cgbc_device_data *cgbc = gpio->cgbc; | 
|  | int ret; | 
|  | u8 val; | 
|  |  | 
|  | scoped_guard(mutex, &gpio->lock) | 
|  | ret = cgbc_gpio_cmd(cgbc, CGBC_GPIO_CMD_DIR_GET, (offset > 7) ? 1 : 0, 0, &val); | 
|  |  | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (val & BIT(offset % 8)) | 
|  | return GPIO_LINE_DIRECTION_OUT; | 
|  | else | 
|  | return GPIO_LINE_DIRECTION_IN; | 
|  | } | 
|  |  | 
|  | static int cgbc_gpio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct cgbc_device_data *cgbc = dev_get_drvdata(dev->parent); | 
|  | struct cgbc_gpio_data *gpio; | 
|  | struct gpio_chip *chip; | 
|  | int ret; | 
|  |  | 
|  | gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); | 
|  | if (!gpio) | 
|  | return -ENOMEM; | 
|  |  | 
|  | gpio->cgbc = cgbc; | 
|  |  | 
|  | platform_set_drvdata(pdev, gpio); | 
|  |  | 
|  | chip = &gpio->chip; | 
|  | chip->label = dev_name(&pdev->dev); | 
|  | chip->owner = THIS_MODULE; | 
|  | chip->parent = dev; | 
|  | chip->base = -1; | 
|  | chip->direction_input = cgbc_gpio_direction_input; | 
|  | chip->direction_output = cgbc_gpio_direction_output; | 
|  | chip->get_direction = cgbc_gpio_get_direction; | 
|  | chip->get = cgbc_gpio_get; | 
|  | chip->set = cgbc_gpio_set; | 
|  | chip->ngpio = CGBC_GPIO_NGPIO; | 
|  |  | 
|  | ret = devm_mutex_init(dev, &gpio->lock); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = devm_gpiochip_add_data(dev, chip, gpio); | 
|  | if (ret) | 
|  | return dev_err_probe(dev, ret, "Could not register GPIO chip\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver cgbc_gpio_driver = { | 
|  | .driver = { | 
|  | .name = "cgbc-gpio", | 
|  | }, | 
|  | .probe	= cgbc_gpio_probe, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(cgbc_gpio_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Congatec Board Controller GPIO Driver"); | 
|  | MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS("platform:cgbc-gpio"); |