175 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Copyright (C) 2015-2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
 | |
|  */
 | |
| 
 | |
| #include <linux/gpio/consumer.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/mod_devicetable.h>
 | |
| #include <linux/platform_device.h>
 | |
| 
 | |
| #include <linux/delay.h>
 | |
| 
 | |
| #include "siox.h"
 | |
| 
 | |
| #define DRIVER_NAME "siox-gpio"
 | |
| 
 | |
| struct siox_gpio_ddata {
 | |
| 	struct gpio_desc *din;
 | |
| 	struct gpio_desc *dout;
 | |
| 	struct gpio_desc *dclk;
 | |
| 	struct gpio_desc *dld;
 | |
| };
 | |
| 
 | |
| static unsigned int siox_clkhigh_ns = 1000;
 | |
| static unsigned int siox_loadhigh_ns;
 | |
| static unsigned int siox_bytegap_ns;
 | |
| 
 | |
| static int siox_gpio_pushpull(struct siox_master *smaster,
 | |
| 			      size_t setbuf_len, const u8 setbuf[],
 | |
| 			      size_t getbuf_len, u8 getbuf[])
 | |
| {
 | |
| 	struct siox_gpio_ddata *ddata = siox_master_get_devdata(smaster);
 | |
| 	size_t i;
 | |
| 	size_t cycles = max(setbuf_len, getbuf_len);
 | |
| 
 | |
| 	/* reset data and clock */
 | |
| 	gpiod_set_value_cansleep(ddata->dout, 0);
 | |
| 	gpiod_set_value_cansleep(ddata->dclk, 0);
 | |
| 
 | |
| 	gpiod_set_value_cansleep(ddata->dld, 1);
 | |
| 	ndelay(siox_loadhigh_ns);
 | |
| 	gpiod_set_value_cansleep(ddata->dld, 0);
 | |
| 
 | |
| 	for (i = 0; i < cycles; ++i) {
 | |
| 		u8 set = 0, get = 0;
 | |
| 		size_t j;
 | |
| 
 | |
| 		if (i >= cycles - setbuf_len)
 | |
| 			set = setbuf[i - (cycles - setbuf_len)];
 | |
| 
 | |
| 		for (j = 0; j < 8; ++j) {
 | |
| 			get <<= 1;
 | |
| 			if (gpiod_get_value_cansleep(ddata->din))
 | |
| 				get |= 1;
 | |
| 
 | |
| 			/* DOUT is logically inverted */
 | |
| 			gpiod_set_value_cansleep(ddata->dout, !(set & 0x80));
 | |
| 			set <<= 1;
 | |
| 
 | |
| 			gpiod_set_value_cansleep(ddata->dclk, 1);
 | |
| 			ndelay(siox_clkhigh_ns);
 | |
| 			gpiod_set_value_cansleep(ddata->dclk, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (i < getbuf_len)
 | |
| 			getbuf[i] = get;
 | |
| 
 | |
| 		ndelay(siox_bytegap_ns);
 | |
| 	}
 | |
| 
 | |
| 	gpiod_set_value_cansleep(ddata->dld, 1);
 | |
| 	ndelay(siox_loadhigh_ns);
 | |
| 	gpiod_set_value_cansleep(ddata->dld, 0);
 | |
| 
 | |
| 	/*
 | |
| 	 * Resetting dout isn't necessary protocol wise, but it makes the
 | |
| 	 * signals more pretty because the dout level is deterministic between
 | |
| 	 * cycles. Note that this only affects dout between the master and the
 | |
| 	 * first siox device. dout for the later devices depend on the output of
 | |
| 	 * the previous siox device.
 | |
| 	 */
 | |
| 	gpiod_set_value_cansleep(ddata->dout, 0);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int siox_gpio_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	struct siox_gpio_ddata *ddata;
 | |
| 	int ret;
 | |
| 	struct siox_master *smaster;
 | |
| 
 | |
| 	smaster = siox_master_alloc(&pdev->dev, sizeof(*ddata));
 | |
| 	if (!smaster) {
 | |
| 		dev_err(dev, "failed to allocate siox master\n");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	platform_set_drvdata(pdev, smaster);
 | |
| 	ddata = siox_master_get_devdata(smaster);
 | |
| 
 | |
| 	ddata->din = devm_gpiod_get(dev, "din", GPIOD_IN);
 | |
| 	if (IS_ERR(ddata->din)) {
 | |
| 		ret = dev_err_probe(dev, PTR_ERR(ddata->din),
 | |
| 				    "Failed to get din GPIO\n");
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	ddata->dout = devm_gpiod_get(dev, "dout", GPIOD_OUT_LOW);
 | |
| 	if (IS_ERR(ddata->dout)) {
 | |
| 		ret = dev_err_probe(dev, PTR_ERR(ddata->dout),
 | |
| 				    "Failed to get dout GPIO\n");
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	ddata->dclk = devm_gpiod_get(dev, "dclk", GPIOD_OUT_LOW);
 | |
| 	if (IS_ERR(ddata->dclk)) {
 | |
| 		ret = dev_err_probe(dev, PTR_ERR(ddata->dclk),
 | |
| 				    "Failed to get dclk GPIO\n");
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	ddata->dld = devm_gpiod_get(dev, "dld", GPIOD_OUT_LOW);
 | |
| 	if (IS_ERR(ddata->dld)) {
 | |
| 		ret = dev_err_probe(dev, PTR_ERR(ddata->dld),
 | |
| 				    "Failed to get dld GPIO\n");
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	smaster->pushpull = siox_gpio_pushpull;
 | |
| 	/* XXX: determine automatically like spi does */
 | |
| 	smaster->busno = 0;
 | |
| 
 | |
| 	ret = siox_master_register(smaster);
 | |
| 	if (ret) {
 | |
| 		dev_err_probe(dev, ret,
 | |
| 			      "Failed to register siox master\n");
 | |
| err:
 | |
| 		siox_master_put(smaster);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int siox_gpio_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	struct siox_master *master = platform_get_drvdata(pdev);
 | |
| 
 | |
| 	siox_master_unregister(master);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct of_device_id siox_gpio_dt_ids[] = {
 | |
| 	{ .compatible = "eckelmann,siox-gpio", },
 | |
| 	{ /* sentinel */ }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, siox_gpio_dt_ids);
 | |
| 
 | |
| static struct platform_driver siox_gpio_driver = {
 | |
| 	.probe = siox_gpio_probe,
 | |
| 	.remove = siox_gpio_remove,
 | |
| 
 | |
| 	.driver = {
 | |
| 		.name = DRIVER_NAME,
 | |
| 		.of_match_table = siox_gpio_dt_ids,
 | |
| 	},
 | |
| };
 | |
| module_platform_driver(siox_gpio_driver);
 | |
| 
 | |
| MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>");
 | |
| MODULE_LICENSE("GPL v2");
 | |
| MODULE_ALIAS("platform:" DRIVER_NAME);
 |