|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | *  Turris Mox Moxtet GPIO expander | 
|  | * | 
|  | *  Copyright (C) 2018 Marek BehĂșn <kabel@kernel.org> | 
|  | */ | 
|  |  | 
|  | #include <linux/bitops.h> | 
|  | #include <linux/gpio/driver.h> | 
|  | #include <linux/moxtet.h> | 
|  | #include <linux/module.h> | 
|  |  | 
|  | #define MOXTET_GPIO_NGPIOS	12 | 
|  | #define MOXTET_GPIO_INPUTS	4 | 
|  |  | 
|  | struct moxtet_gpio_desc { | 
|  | u16 in_mask; | 
|  | u16 out_mask; | 
|  | }; | 
|  |  | 
|  | static const struct moxtet_gpio_desc descs[] = { | 
|  | [TURRIS_MOX_MODULE_SFP] = { | 
|  | .in_mask = GENMASK(2, 0), | 
|  | .out_mask = GENMASK(5, 4), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | struct moxtet_gpio_chip { | 
|  | struct device			*dev; | 
|  | struct gpio_chip		gpio_chip; | 
|  | const struct moxtet_gpio_desc	*desc; | 
|  | }; | 
|  |  | 
|  | static int moxtet_gpio_get_value(struct gpio_chip *gc, unsigned int offset) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); | 
|  | int ret; | 
|  |  | 
|  | if (chip->desc->in_mask & BIT(offset)) { | 
|  | ret = moxtet_device_read(chip->dev); | 
|  | } else if (chip->desc->out_mask & BIT(offset)) { | 
|  | ret = moxtet_device_written(chip->dev); | 
|  | if (ret >= 0) | 
|  | ret <<= MOXTET_GPIO_INPUTS; | 
|  | } else { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | return !!(ret & BIT(offset)); | 
|  | } | 
|  |  | 
|  | static int moxtet_gpio_set_value(struct gpio_chip *gc, unsigned int offset, | 
|  | int val) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); | 
|  | int state; | 
|  |  | 
|  | state = moxtet_device_written(chip->dev); | 
|  | if (state < 0) | 
|  | return state; | 
|  |  | 
|  | offset -= MOXTET_GPIO_INPUTS; | 
|  |  | 
|  | if (val) | 
|  | state |= BIT(offset); | 
|  | else | 
|  | state &= ~BIT(offset); | 
|  |  | 
|  | return moxtet_device_write(chip->dev, state); | 
|  | } | 
|  |  | 
|  | static int moxtet_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); | 
|  |  | 
|  | /* All lines are hard wired to be either input or output, not both. */ | 
|  | if (chip->desc->in_mask & BIT(offset)) | 
|  | return GPIO_LINE_DIRECTION_IN; | 
|  | else if (chip->desc->out_mask & BIT(offset)) | 
|  | return GPIO_LINE_DIRECTION_OUT; | 
|  | else | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int moxtet_gpio_direction_input(struct gpio_chip *gc, | 
|  | unsigned int offset) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); | 
|  |  | 
|  | if (chip->desc->in_mask & BIT(offset)) | 
|  | return 0; | 
|  | else if (chip->desc->out_mask & BIT(offset)) | 
|  | return -ENOTSUPP; | 
|  | else | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int moxtet_gpio_direction_output(struct gpio_chip *gc, | 
|  | unsigned int offset, int val) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip = gpiochip_get_data(gc); | 
|  |  | 
|  | if (chip->desc->out_mask & BIT(offset)) | 
|  | return moxtet_gpio_set_value(gc, offset, val); | 
|  | else if (chip->desc->in_mask & BIT(offset)) | 
|  | return -ENOTSUPP; | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int moxtet_gpio_probe(struct device *dev) | 
|  | { | 
|  | struct moxtet_gpio_chip *chip; | 
|  | struct device_node *nc = dev->of_node; | 
|  | int id; | 
|  |  | 
|  | id = to_moxtet_device(dev)->id; | 
|  |  | 
|  | if (id >= ARRAY_SIZE(descs)) { | 
|  | dev_err(dev, "%pOF Moxtet device id 0x%x is not supported by gpio-moxtet driver\n", | 
|  | nc, id); | 
|  | return -ENOTSUPP; | 
|  | } | 
|  |  | 
|  | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); | 
|  | if (!chip) | 
|  | return -ENOMEM; | 
|  |  | 
|  | chip->dev = dev; | 
|  | chip->gpio_chip.parent = dev; | 
|  | chip->desc = &descs[id]; | 
|  |  | 
|  | dev_set_drvdata(dev, chip); | 
|  |  | 
|  | chip->gpio_chip.label = dev_name(dev); | 
|  | chip->gpio_chip.get_direction = moxtet_gpio_get_direction; | 
|  | chip->gpio_chip.direction_input = moxtet_gpio_direction_input; | 
|  | chip->gpio_chip.direction_output = moxtet_gpio_direction_output; | 
|  | chip->gpio_chip.get = moxtet_gpio_get_value; | 
|  | chip->gpio_chip.set = moxtet_gpio_set_value; | 
|  | chip->gpio_chip.base = -1; | 
|  |  | 
|  | chip->gpio_chip.ngpio = MOXTET_GPIO_NGPIOS; | 
|  |  | 
|  | chip->gpio_chip.can_sleep = true; | 
|  | chip->gpio_chip.owner = THIS_MODULE; | 
|  |  | 
|  | return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id moxtet_gpio_dt_ids[] = { | 
|  | { .compatible = "cznic,moxtet-gpio", }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, moxtet_gpio_dt_ids); | 
|  |  | 
|  | static const enum turris_mox_module_id moxtet_gpio_module_table[] = { | 
|  | TURRIS_MOX_MODULE_SFP, | 
|  | 0, | 
|  | }; | 
|  |  | 
|  | static struct moxtet_driver moxtet_gpio_driver = { | 
|  | .driver = { | 
|  | .name		= "moxtet-gpio", | 
|  | .of_match_table	= moxtet_gpio_dt_ids, | 
|  | .probe		= moxtet_gpio_probe, | 
|  | }, | 
|  | .id_table = moxtet_gpio_module_table, | 
|  | }; | 
|  | module_moxtet_driver(moxtet_gpio_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); | 
|  | MODULE_DESCRIPTION("Turris Mox Moxtet GPIO expander"); | 
|  | MODULE_LICENSE("GPL v2"); |