18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Turris Mox Moxtet GPIO expander
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2018 Marek Behun <marek.behun@nic.cz>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/bitops.h>
98c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
108c2ecf20Sopenharmony_ci#include <linux/moxtet.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define MOXTET_GPIO_NGPIOS	12
148c2ecf20Sopenharmony_ci#define MOXTET_GPIO_INPUTS	4
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistruct moxtet_gpio_desc {
178c2ecf20Sopenharmony_ci	u16 in_mask;
188c2ecf20Sopenharmony_ci	u16 out_mask;
198c2ecf20Sopenharmony_ci};
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistatic const struct moxtet_gpio_desc descs[] = {
228c2ecf20Sopenharmony_ci	[TURRIS_MOX_MODULE_SFP] = {
238c2ecf20Sopenharmony_ci		.in_mask = GENMASK(2, 0),
248c2ecf20Sopenharmony_ci		.out_mask = GENMASK(5, 4),
258c2ecf20Sopenharmony_ci	},
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistruct moxtet_gpio_chip {
298c2ecf20Sopenharmony_ci	struct device			*dev;
308c2ecf20Sopenharmony_ci	struct gpio_chip		gpio_chip;
318c2ecf20Sopenharmony_ci	const struct moxtet_gpio_desc	*desc;
328c2ecf20Sopenharmony_ci};
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistatic int moxtet_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
358c2ecf20Sopenharmony_ci{
368c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
378c2ecf20Sopenharmony_ci	int ret;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	if (chip->desc->in_mask & BIT(offset)) {
408c2ecf20Sopenharmony_ci		ret = moxtet_device_read(chip->dev);
418c2ecf20Sopenharmony_ci	} else if (chip->desc->out_mask & BIT(offset)) {
428c2ecf20Sopenharmony_ci		ret = moxtet_device_written(chip->dev);
438c2ecf20Sopenharmony_ci		if (ret >= 0)
448c2ecf20Sopenharmony_ci			ret <<= MOXTET_GPIO_INPUTS;
458c2ecf20Sopenharmony_ci	} else {
468c2ecf20Sopenharmony_ci		return -EINVAL;
478c2ecf20Sopenharmony_ci	}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	if (ret < 0)
508c2ecf20Sopenharmony_ci		return ret;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	return !!(ret & BIT(offset));
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic void moxtet_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
568c2ecf20Sopenharmony_ci				  int val)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
598c2ecf20Sopenharmony_ci	int state;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	state = moxtet_device_written(chip->dev);
628c2ecf20Sopenharmony_ci	if (state < 0)
638c2ecf20Sopenharmony_ci		return;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	offset -= MOXTET_GPIO_INPUTS;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	if (val)
688c2ecf20Sopenharmony_ci		state |= BIT(offset);
698c2ecf20Sopenharmony_ci	else
708c2ecf20Sopenharmony_ci		state &= ~BIT(offset);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	moxtet_device_write(chip->dev, state);
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic int moxtet_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	/* All lines are hard wired to be either input or output, not both. */
808c2ecf20Sopenharmony_ci	if (chip->desc->in_mask & BIT(offset))
818c2ecf20Sopenharmony_ci		return GPIO_LINE_DIRECTION_IN;
828c2ecf20Sopenharmony_ci	else if (chip->desc->out_mask & BIT(offset))
838c2ecf20Sopenharmony_ci		return GPIO_LINE_DIRECTION_OUT;
848c2ecf20Sopenharmony_ci	else
858c2ecf20Sopenharmony_ci		return -EINVAL;
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic int moxtet_gpio_direction_input(struct gpio_chip *gc,
898c2ecf20Sopenharmony_ci				       unsigned int offset)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	if (chip->desc->in_mask & BIT(offset))
948c2ecf20Sopenharmony_ci		return 0;
958c2ecf20Sopenharmony_ci	else if (chip->desc->out_mask & BIT(offset))
968c2ecf20Sopenharmony_ci		return -ENOTSUPP;
978c2ecf20Sopenharmony_ci	else
988c2ecf20Sopenharmony_ci		return -EINVAL;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int moxtet_gpio_direction_output(struct gpio_chip *gc,
1028c2ecf20Sopenharmony_ci					unsigned int offset, int val)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip = gpiochip_get_data(gc);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (chip->desc->out_mask & BIT(offset))
1078c2ecf20Sopenharmony_ci		moxtet_gpio_set_value(gc, offset, val);
1088c2ecf20Sopenharmony_ci	else if (chip->desc->in_mask & BIT(offset))
1098c2ecf20Sopenharmony_ci		return -ENOTSUPP;
1108c2ecf20Sopenharmony_ci	else
1118c2ecf20Sopenharmony_ci		return -EINVAL;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	return 0;
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int moxtet_gpio_probe(struct device *dev)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	struct moxtet_gpio_chip *chip;
1198c2ecf20Sopenharmony_ci	struct device_node *nc = dev->of_node;
1208c2ecf20Sopenharmony_ci	int id;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	id = to_moxtet_device(dev)->id;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (id >= ARRAY_SIZE(descs)) {
1258c2ecf20Sopenharmony_ci		dev_err(dev, "%pOF Moxtet device id 0x%x is not supported by gpio-moxtet driver\n",
1268c2ecf20Sopenharmony_ci			nc, id);
1278c2ecf20Sopenharmony_ci		return -ENOTSUPP;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
1318c2ecf20Sopenharmony_ci	if (!chip)
1328c2ecf20Sopenharmony_ci		return -ENOMEM;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	chip->dev = dev;
1358c2ecf20Sopenharmony_ci	chip->gpio_chip.parent = dev;
1368c2ecf20Sopenharmony_ci	chip->desc = &descs[id];
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, chip);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	chip->gpio_chip.label = dev_name(dev);
1418c2ecf20Sopenharmony_ci	chip->gpio_chip.get_direction = moxtet_gpio_get_direction;
1428c2ecf20Sopenharmony_ci	chip->gpio_chip.direction_input = moxtet_gpio_direction_input;
1438c2ecf20Sopenharmony_ci	chip->gpio_chip.direction_output = moxtet_gpio_direction_output;
1448c2ecf20Sopenharmony_ci	chip->gpio_chip.get = moxtet_gpio_get_value;
1458c2ecf20Sopenharmony_ci	chip->gpio_chip.set = moxtet_gpio_set_value;
1468c2ecf20Sopenharmony_ci	chip->gpio_chip.base = -1;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	chip->gpio_chip.ngpio = MOXTET_GPIO_NGPIOS;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	chip->gpio_chip.can_sleep = true;
1518c2ecf20Sopenharmony_ci	chip->gpio_chip.owner = THIS_MODULE;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip);
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic const struct of_device_id moxtet_gpio_dt_ids[] = {
1578c2ecf20Sopenharmony_ci	{ .compatible = "cznic,moxtet-gpio", },
1588c2ecf20Sopenharmony_ci	{},
1598c2ecf20Sopenharmony_ci};
1608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, moxtet_gpio_dt_ids);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic const enum turris_mox_module_id moxtet_gpio_module_table[] = {
1638c2ecf20Sopenharmony_ci	TURRIS_MOX_MODULE_SFP,
1648c2ecf20Sopenharmony_ci	0,
1658c2ecf20Sopenharmony_ci};
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic struct moxtet_driver moxtet_gpio_driver = {
1688c2ecf20Sopenharmony_ci	.driver = {
1698c2ecf20Sopenharmony_ci		.name		= "moxtet-gpio",
1708c2ecf20Sopenharmony_ci		.of_match_table	= moxtet_gpio_dt_ids,
1718c2ecf20Sopenharmony_ci		.probe		= moxtet_gpio_probe,
1728c2ecf20Sopenharmony_ci	},
1738c2ecf20Sopenharmony_ci	.id_table = moxtet_gpio_module_table,
1748c2ecf20Sopenharmony_ci};
1758c2ecf20Sopenharmony_cimodule_moxtet_driver(moxtet_gpio_driver);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ciMODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>");
1788c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Turris Mox Moxtet GPIO expander");
1798c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
180