18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
78c2ecf20Sopenharmony_ci#include <linux/module.h>
88c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h>
98c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "siox.h"
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define DRIVER_NAME "siox-gpio"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistruct siox_gpio_ddata {
188c2ecf20Sopenharmony_ci	struct gpio_desc *din;
198c2ecf20Sopenharmony_ci	struct gpio_desc *dout;
208c2ecf20Sopenharmony_ci	struct gpio_desc *dclk;
218c2ecf20Sopenharmony_ci	struct gpio_desc *dld;
228c2ecf20Sopenharmony_ci};
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic unsigned int siox_clkhigh_ns = 1000;
258c2ecf20Sopenharmony_cistatic unsigned int siox_loadhigh_ns;
268c2ecf20Sopenharmony_cistatic unsigned int siox_bytegap_ns;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int siox_gpio_pushpull(struct siox_master *smaster,
298c2ecf20Sopenharmony_ci			      size_t setbuf_len, const u8 setbuf[],
308c2ecf20Sopenharmony_ci			      size_t getbuf_len, u8 getbuf[])
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	struct siox_gpio_ddata *ddata = siox_master_get_devdata(smaster);
338c2ecf20Sopenharmony_ci	size_t i;
348c2ecf20Sopenharmony_ci	size_t cycles = max(setbuf_len, getbuf_len);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	/* reset data and clock */
378c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dout, 0);
388c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dclk, 0);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dld, 1);
418c2ecf20Sopenharmony_ci	ndelay(siox_loadhigh_ns);
428c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dld, 0);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	for (i = 0; i < cycles; ++i) {
458c2ecf20Sopenharmony_ci		u8 set = 0, get = 0;
468c2ecf20Sopenharmony_ci		size_t j;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci		if (i >= cycles - setbuf_len)
498c2ecf20Sopenharmony_ci			set = setbuf[i - (cycles - setbuf_len)];
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci		for (j = 0; j < 8; ++j) {
528c2ecf20Sopenharmony_ci			get <<= 1;
538c2ecf20Sopenharmony_ci			if (gpiod_get_value_cansleep(ddata->din))
548c2ecf20Sopenharmony_ci				get |= 1;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci			/* DOUT is logically inverted */
578c2ecf20Sopenharmony_ci			gpiod_set_value_cansleep(ddata->dout, !(set & 0x80));
588c2ecf20Sopenharmony_ci			set <<= 1;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci			gpiod_set_value_cansleep(ddata->dclk, 1);
618c2ecf20Sopenharmony_ci			ndelay(siox_clkhigh_ns);
628c2ecf20Sopenharmony_ci			gpiod_set_value_cansleep(ddata->dclk, 0);
638c2ecf20Sopenharmony_ci		}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci		if (i < getbuf_len)
668c2ecf20Sopenharmony_ci			getbuf[i] = get;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci		ndelay(siox_bytegap_ns);
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dld, 1);
728c2ecf20Sopenharmony_ci	ndelay(siox_loadhigh_ns);
738c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dld, 0);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	/*
768c2ecf20Sopenharmony_ci	 * Resetting dout isn't necessary protocol wise, but it makes the
778c2ecf20Sopenharmony_ci	 * signals more pretty because the dout level is deterministic between
788c2ecf20Sopenharmony_ci	 * cycles. Note that this only affects dout between the master and the
798c2ecf20Sopenharmony_ci	 * first siox device. dout for the later devices depend on the output of
808c2ecf20Sopenharmony_ci	 * the previous siox device.
818c2ecf20Sopenharmony_ci	 */
828c2ecf20Sopenharmony_ci	gpiod_set_value_cansleep(ddata->dout, 0);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return 0;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int siox_gpio_probe(struct platform_device *pdev)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
908c2ecf20Sopenharmony_ci	struct siox_gpio_ddata *ddata;
918c2ecf20Sopenharmony_ci	int ret;
928c2ecf20Sopenharmony_ci	struct siox_master *smaster;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	smaster = siox_master_alloc(&pdev->dev, sizeof(*ddata));
958c2ecf20Sopenharmony_ci	if (!smaster) {
968c2ecf20Sopenharmony_ci		dev_err(dev, "failed to allocate siox master\n");
978c2ecf20Sopenharmony_ci		return -ENOMEM;
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, smaster);
1018c2ecf20Sopenharmony_ci	ddata = siox_master_get_devdata(smaster);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	ddata->din = devm_gpiod_get(dev, "din", GPIOD_IN);
1048c2ecf20Sopenharmony_ci	if (IS_ERR(ddata->din)) {
1058c2ecf20Sopenharmony_ci		ret = PTR_ERR(ddata->din);
1068c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get %s GPIO: %d\n", "din", ret);
1078c2ecf20Sopenharmony_ci		goto err;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	ddata->dout = devm_gpiod_get(dev, "dout", GPIOD_OUT_LOW);
1118c2ecf20Sopenharmony_ci	if (IS_ERR(ddata->dout)) {
1128c2ecf20Sopenharmony_ci		ret = PTR_ERR(ddata->dout);
1138c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get %s GPIO: %d\n", "dout", ret);
1148c2ecf20Sopenharmony_ci		goto err;
1158c2ecf20Sopenharmony_ci	}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	ddata->dclk = devm_gpiod_get(dev, "dclk", GPIOD_OUT_LOW);
1188c2ecf20Sopenharmony_ci	if (IS_ERR(ddata->dclk)) {
1198c2ecf20Sopenharmony_ci		ret = PTR_ERR(ddata->dclk);
1208c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get %s GPIO: %d\n", "dclk", ret);
1218c2ecf20Sopenharmony_ci		goto err;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	ddata->dld = devm_gpiod_get(dev, "dld", GPIOD_OUT_LOW);
1258c2ecf20Sopenharmony_ci	if (IS_ERR(ddata->dld)) {
1268c2ecf20Sopenharmony_ci		ret = PTR_ERR(ddata->dld);
1278c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get %s GPIO: %d\n", "dld", ret);
1288c2ecf20Sopenharmony_ci		goto err;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	smaster->pushpull = siox_gpio_pushpull;
1328c2ecf20Sopenharmony_ci	/* XXX: determine automatically like spi does */
1338c2ecf20Sopenharmony_ci	smaster->busno = 0;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	ret = siox_master_register(smaster);
1368c2ecf20Sopenharmony_ci	if (ret) {
1378c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to register siox master: %d\n", ret);
1388c2ecf20Sopenharmony_cierr:
1398c2ecf20Sopenharmony_ci		siox_master_put(smaster);
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return ret;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int siox_gpio_remove(struct platform_device *pdev)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct siox_master *master = platform_get_drvdata(pdev);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	siox_master_unregister(master);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	return 0;
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic const struct of_device_id siox_gpio_dt_ids[] = {
1558c2ecf20Sopenharmony_ci	{ .compatible = "eckelmann,siox-gpio", },
1568c2ecf20Sopenharmony_ci	{ /* sentinel */ }
1578c2ecf20Sopenharmony_ci};
1588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, siox_gpio_dt_ids);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic struct platform_driver siox_gpio_driver = {
1618c2ecf20Sopenharmony_ci	.probe = siox_gpio_probe,
1628c2ecf20Sopenharmony_ci	.remove = siox_gpio_remove,
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	.driver = {
1658c2ecf20Sopenharmony_ci		.name = DRIVER_NAME,
1668c2ecf20Sopenharmony_ci		.of_match_table = siox_gpio_dt_ids,
1678c2ecf20Sopenharmony_ci	},
1688c2ecf20Sopenharmony_ci};
1698c2ecf20Sopenharmony_cimodule_platform_driver(siox_gpio_driver);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
1728c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1738c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME);
174