brintos

brintos / linux-shallow public Read only

0
0
Text · 11.8 KiB · 90f8e9e Raw
445 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Digital I/O driver for Technologic Systems TS-55004 *5 * Copyright (c) 2012 Savoir-faire Linux Inc.6 *	Vivien Didelot <vivien.didelot@savoirfairelinux.com>7 *8 * Technologic Systems platforms have pin blocks, exposing several Digital9 * Input/Output lines (DIO). This driver aims to support single pin blocks.10 * In that sense, the support is not limited to the TS-5500 blocks.11 * Actually, the following platforms have DIO support:12 *13 * TS-5500:14 *   Documentation: https://docs.embeddedts.com/TS-550015 *   Blocks: DIO1, DIO2 and LCD port.16 *17 * TS-5600:18 *   Documentation: https://docs.embeddedts.com/TS-560019 *   Blocks: LCD port (identical to TS-5500 LCD).20 */21 22#include <linux/bitops.h>23#include <linux/gpio/driver.h>24#include <linux/io.h>25#include <linux/module.h>26#include <linux/platform_device.h>27#include <linux/slab.h>28 29/* List of supported Technologic Systems platforms DIO blocks */30enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD };31 32struct ts5500_priv {33	const struct ts5500_dio *pinout;34	struct gpio_chip gpio_chip;35	spinlock_t lock;36	bool strap;37	u8 hwirq;38};39 40/*41 * Hex 7D is used to control several blocks (e.g. DIO2 and LCD port).42 * This flag ensures that the region has been requested by this driver.43 */44static bool hex7d_reserved;45 46/*47 * This structure is used to describe capabilities of DIO lines,48 * such as available directions and connected interrupt (if any).49 */50struct ts5500_dio {51	const u8 value_addr;52	const u8 value_mask;53	const u8 control_addr;54	const u8 control_mask;55	const bool no_input;56	const bool no_output;57	const u8 irq;58};59 60#define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit)	\61	{						\62		.value_addr = vaddr,			\63		.value_mask = BIT(vbit),		\64		.control_addr = caddr,			\65		.control_mask = BIT(cbit),		\66	}67 68#define TS5500_DIO_IN(addr, bit)		\69	{					\70		.value_addr = addr,		\71		.value_mask = BIT(bit),		\72		.no_output = true,		\73	}74 75#define TS5500_DIO_IN_IRQ(addr, bit, _irq)	\76	{					\77		.value_addr = addr,		\78		.value_mask = BIT(bit),		\79		.no_output = true,		\80		.irq = _irq,			\81	}82 83#define TS5500_DIO_OUT(addr, bit)		\84	{					\85		.value_addr = addr,		\86		.value_mask = BIT(bit),		\87		.no_input = true,		\88	}89 90/*91 * Input/Output DIO lines are programmed in groups of 4. Their values are92 * available through 4 consecutive bits in a value port, whereas the direction93 * of these 4 lines is driven by only 1 bit in a control port.94 */95#define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit)		\96	TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit),	\97	TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit),	\98	TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit),	\99	TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit)100 101/*102 * TS-5500 DIO1 block103 *104 *  value    control  dir    hw105 *  addr bit addr bit in out irq name     pin offset106 *107 *  0x7b  0  0x7a  0  x   x      DIO1_0   1   0108 *  0x7b  1  0x7a  0  x   x      DIO1_1   3   1109 *  0x7b  2  0x7a  0  x   x      DIO1_2   5   2110 *  0x7b  3  0x7a  0  x   x      DIO1_3   7   3111 *  0x7b  4  0x7a  1  x   x      DIO1_4   9   4112 *  0x7b  5  0x7a  1  x   x      DIO1_5   11  5113 *  0x7b  6  0x7a  1  x   x      DIO1_6   13  6114 *  0x7b  7  0x7a  1  x   x      DIO1_7   15  7115 *  0x7c  0  0x7a  5  x   x      DIO1_8   4   8116 *  0x7c  1  0x7a  5  x   x      DIO1_9   6   9117 *  0x7c  2  0x7a  5  x   x      DIO1_10  8   10118 *  0x7c  3  0x7a  5  x   x      DIO1_11  10  11119 *  0x7c  4           x          DIO1_12  12  12120 *  0x7c  5           x      7   DIO1_13  14  13121 */122static const struct ts5500_dio ts5500_dio1[] = {123	TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0),124	TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1),125	TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5),126	TS5500_DIO_IN(0x7c, 4),127	TS5500_DIO_IN_IRQ(0x7c, 5, 7),128};129 130/*131 * TS-5500 DIO2 block132 *133 *  value    control  dir    hw134 *  addr bit addr bit in out irq name     pin offset135 *136 *  0x7e  0  0x7d  0  x   x      DIO2_0   1   0137 *  0x7e  1  0x7d  0  x   x      DIO2_1   3   1138 *  0x7e  2  0x7d  0  x   x      DIO2_2   5   2139 *  0x7e  3  0x7d  0  x   x      DIO2_3   7   3140 *  0x7e  4  0x7d  1  x   x      DIO2_4   9   4141 *  0x7e  5  0x7d  1  x   x      DIO2_5   11  5142 *  0x7e  6  0x7d  1  x   x      DIO2_6   13  6143 *  0x7e  7  0x7d  1  x   x      DIO2_7   15  7144 *  0x7f  0  0x7d  5  x   x      DIO2_8   4   8145 *  0x7f  1  0x7d  5  x   x      DIO2_9   6   9146 *  0x7f  2  0x7d  5  x   x      DIO2_10  8   10147 *  0x7f  3  0x7d  5  x   x      DIO2_11  10  11148 *  0x7f  4           x      6   DIO2_13  14  12149 */150static const struct ts5500_dio ts5500_dio2[] = {151	TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0),152	TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1),153	TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5),154	TS5500_DIO_IN_IRQ(0x7f, 4, 6),155};156 157/*158 * TS-5500 LCD port used as DIO block159 * TS-5600 LCD port is identical160 *161 *  value    control  dir    hw162 *  addr bit addr bit in out irq name    pin offset163 *164 *  0x72  0  0x7d  2  x   x      LCD_0   8   0165 *  0x72  1  0x7d  2  x   x      LCD_1   7   1166 *  0x72  2  0x7d  2  x   x      LCD_2   10  2167 *  0x72  3  0x7d  2  x   x      LCD_3   9   3168 *  0x72  4  0x7d  3  x   x      LCD_4   12  4169 *  0x72  5  0x7d  3  x   x      LCD_5   11  5170 *  0x72  6  0x7d  3  x   x      LCD_6   14  6171 *  0x72  7  0x7d  3  x   x      LCD_7   13  7172 *  0x73  0               x      LCD_EN  5   8173 *  0x73  6           x          LCD_WR  6   9174 *  0x73  7           x      1   LCD_RS  3   10175 */176static const struct ts5500_dio ts5500_lcd[] = {177	TS5500_DIO_GROUP(0x72, 0, 0x7d, 2),178	TS5500_DIO_GROUP(0x72, 4, 0x7d, 3),179	TS5500_DIO_OUT(0x73, 0),180	TS5500_DIO_IN(0x73, 6),181	TS5500_DIO_IN_IRQ(0x73, 7, 1),182};183 184static inline void ts5500_set_mask(u8 mask, u8 addr)185{186	u8 val = inb(addr);187	val |= mask;188	outb(val, addr);189}190 191static inline void ts5500_clear_mask(u8 mask, u8 addr)192{193	u8 val = inb(addr);194	val &= ~mask;195	outb(val, addr);196}197 198static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset)199{200	struct ts5500_priv *priv = gpiochip_get_data(chip);201	const struct ts5500_dio line = priv->pinout[offset];202	unsigned long flags;203 204	if (line.no_input)205		return -ENXIO;206 207	if (line.no_output)208		return 0;209 210	spin_lock_irqsave(&priv->lock, flags);211	ts5500_clear_mask(line.control_mask, line.control_addr);212	spin_unlock_irqrestore(&priv->lock, flags);213 214	return 0;215}216 217static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset)218{219	struct ts5500_priv *priv = gpiochip_get_data(chip);220	const struct ts5500_dio line = priv->pinout[offset];221 222	return !!(inb(line.value_addr) & line.value_mask);223}224 225static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val)226{227	struct ts5500_priv *priv = gpiochip_get_data(chip);228	const struct ts5500_dio line = priv->pinout[offset];229	unsigned long flags;230 231	if (line.no_output)232		return -ENXIO;233 234	spin_lock_irqsave(&priv->lock, flags);235	if (!line.no_input)236		ts5500_set_mask(line.control_mask, line.control_addr);237 238	if (val)239		ts5500_set_mask(line.value_mask, line.value_addr);240	else241		ts5500_clear_mask(line.value_mask, line.value_addr);242	spin_unlock_irqrestore(&priv->lock, flags);243 244	return 0;245}246 247static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)248{249	struct ts5500_priv *priv = gpiochip_get_data(chip);250	const struct ts5500_dio line = priv->pinout[offset];251	unsigned long flags;252 253	spin_lock_irqsave(&priv->lock, flags);254	if (val)255		ts5500_set_mask(line.value_mask, line.value_addr);256	else257		ts5500_clear_mask(line.value_mask, line.value_addr);258	spin_unlock_irqrestore(&priv->lock, flags);259}260 261static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)262{263	struct ts5500_priv *priv = gpiochip_get_data(chip);264	const struct ts5500_dio *block = priv->pinout;265	const struct ts5500_dio line = block[offset];266 267	/* Only one pin is connected to an interrupt */268	if (line.irq)269		return line.irq;270 271	/* As this pin is input-only, we may strap it to another in/out pin */272	if (priv->strap)273		return priv->hwirq;274 275	return -ENXIO;276}277 278static int ts5500_enable_irq(struct ts5500_priv *priv)279{280	int ret = 0;281	unsigned long flags;282 283	spin_lock_irqsave(&priv->lock, flags);284	if (priv->hwirq == 7)285		ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */286	else if (priv->hwirq == 6)287		ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */288	else if (priv->hwirq == 1)289		ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */290	else291		ret = -EINVAL;292	spin_unlock_irqrestore(&priv->lock, flags);293 294	return ret;295}296 297static void ts5500_disable_irq(struct ts5500_priv *priv)298{299	unsigned long flags;300 301	spin_lock_irqsave(&priv->lock, flags);302	if (priv->hwirq == 7)303		ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */304	else if (priv->hwirq == 6)305		ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */306	else if (priv->hwirq == 1)307		ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */308	else309		dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n",310			priv->hwirq);311	spin_unlock_irqrestore(&priv->lock, flags);312}313 314static int ts5500_dio_probe(struct platform_device *pdev)315{316	enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data;317	struct device *dev = &pdev->dev;318	const char *name = dev_name(dev);319	struct ts5500_priv *priv;320	unsigned long flags;321	int ret;322 323	ret = platform_get_irq(pdev, 0);324	if (ret < 0)325		return ret;326 327	priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL);328	if (!priv)329		return -ENOMEM;330 331	platform_set_drvdata(pdev, priv);332	priv->hwirq = ret;333	spin_lock_init(&priv->lock);334 335	priv->gpio_chip.owner = THIS_MODULE;336	priv->gpio_chip.label = name;337	priv->gpio_chip.parent = dev;338	priv->gpio_chip.direction_input = ts5500_gpio_input;339	priv->gpio_chip.direction_output = ts5500_gpio_output;340	priv->gpio_chip.get = ts5500_gpio_get;341	priv->gpio_chip.set = ts5500_gpio_set;342	priv->gpio_chip.to_irq = ts5500_gpio_to_irq;343	priv->gpio_chip.base = -1;344 345	switch (block) {346	case TS5500_DIO1:347		priv->pinout = ts5500_dio1;348		priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1);349 350		if (!devm_request_region(dev, 0x7a, 3, name)) {351			dev_err(dev, "failed to request %s ports\n", name);352			return -EBUSY;353		}354		break;355	case TS5500_DIO2:356		priv->pinout = ts5500_dio2;357		priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2);358 359		if (!devm_request_region(dev, 0x7e, 2, name)) {360			dev_err(dev, "failed to request %s ports\n", name);361			return -EBUSY;362		}363 364		if (hex7d_reserved)365			break;366 367		if (!devm_request_region(dev, 0x7d, 1, name)) {368			dev_err(dev, "failed to request %s 7D\n", name);369			return -EBUSY;370		}371 372		hex7d_reserved = true;373		break;374	case TS5500_LCD:375	case TS5600_LCD:376		priv->pinout = ts5500_lcd;377		priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd);378 379		if (!devm_request_region(dev, 0x72, 2, name)) {380			dev_err(dev, "failed to request %s ports\n", name);381			return -EBUSY;382		}383 384		if (!hex7d_reserved) {385			if (!devm_request_region(dev, 0x7d, 1, name)) {386				dev_err(dev, "failed to request %s 7D\n", name);387				return -EBUSY;388			}389 390			hex7d_reserved = true;391		}392 393		/* Ensure usage of LCD port as DIO */394		spin_lock_irqsave(&priv->lock, flags);395		ts5500_clear_mask(BIT(4), 0x7d);396		spin_unlock_irqrestore(&priv->lock, flags);397		break;398	}399 400	ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);401	if (ret) {402		dev_err(dev, "failed to register the gpio chip\n");403		return ret;404	}405 406	ret = ts5500_enable_irq(priv);407	if (ret) {408		dev_err(dev, "invalid interrupt %d\n", priv->hwirq);409		return ret;410	}411 412	return 0;413}414 415static void ts5500_dio_remove(struct platform_device *pdev)416{417	struct ts5500_priv *priv = platform_get_drvdata(pdev);418 419	ts5500_disable_irq(priv);420}421 422static const struct platform_device_id ts5500_dio_ids[] = {423	{ "ts5500-dio1", TS5500_DIO1 },424	{ "ts5500-dio2", TS5500_DIO2 },425	{ "ts5500-dio-lcd", TS5500_LCD },426	{ "ts5600-dio-lcd", TS5600_LCD },427	{ }428};429MODULE_DEVICE_TABLE(platform, ts5500_dio_ids);430 431static struct platform_driver ts5500_dio_driver = {432	.driver = {433		.name = "ts5500-dio",434	},435	.probe = ts5500_dio_probe,436	.remove_new = ts5500_dio_remove,437	.id_table = ts5500_dio_ids,438};439 440module_platform_driver(ts5500_dio_driver);441 442MODULE_LICENSE("GPL");443MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");444MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver");445