cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

gpio-ts5500.c (12085B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Digital I/O driver for Technologic Systems TS-5500
      4 *
      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 Digital
      9 * 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-5500
     15 *   Blocks: DIO1, DIO2 and LCD port.
     16 *
     17 * TS-5600:
     18 *   Documentation: https://docs.embeddedts.com/TS-5600
     19 *   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 are
     92 * available through 4 consecutive bits in a value port, whereas the direction
     93 * 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 block
    103 *
    104 *  value    control  dir    hw
    105 *  addr bit addr bit in out irq name     pin offset
    106 *
    107 *  0x7b  0  0x7a  0  x   x      DIO1_0   1   0
    108 *  0x7b  1  0x7a  0  x   x      DIO1_1   3   1
    109 *  0x7b  2  0x7a  0  x   x      DIO1_2   5   2
    110 *  0x7b  3  0x7a  0  x   x      DIO1_3   7   3
    111 *  0x7b  4  0x7a  1  x   x      DIO1_4   9   4
    112 *  0x7b  5  0x7a  1  x   x      DIO1_5   11  5
    113 *  0x7b  6  0x7a  1  x   x      DIO1_6   13  6
    114 *  0x7b  7  0x7a  1  x   x      DIO1_7   15  7
    115 *  0x7c  0  0x7a  5  x   x      DIO1_8   4   8
    116 *  0x7c  1  0x7a  5  x   x      DIO1_9   6   9
    117 *  0x7c  2  0x7a  5  x   x      DIO1_10  8   10
    118 *  0x7c  3  0x7a  5  x   x      DIO1_11  10  11
    119 *  0x7c  4           x          DIO1_12  12  12
    120 *  0x7c  5           x      7   DIO1_13  14  13
    121 */
    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 block
    132 *
    133 *  value    control  dir    hw
    134 *  addr bit addr bit in out irq name     pin offset
    135 *
    136 *  0x7e  0  0x7d  0  x   x      DIO2_0   1   0
    137 *  0x7e  1  0x7d  0  x   x      DIO2_1   3   1
    138 *  0x7e  2  0x7d  0  x   x      DIO2_2   5   2
    139 *  0x7e  3  0x7d  0  x   x      DIO2_3   7   3
    140 *  0x7e  4  0x7d  1  x   x      DIO2_4   9   4
    141 *  0x7e  5  0x7d  1  x   x      DIO2_5   11  5
    142 *  0x7e  6  0x7d  1  x   x      DIO2_6   13  6
    143 *  0x7e  7  0x7d  1  x   x      DIO2_7   15  7
    144 *  0x7f  0  0x7d  5  x   x      DIO2_8   4   8
    145 *  0x7f  1  0x7d  5  x   x      DIO2_9   6   9
    146 *  0x7f  2  0x7d  5  x   x      DIO2_10  8   10
    147 *  0x7f  3  0x7d  5  x   x      DIO2_11  10  11
    148 *  0x7f  4           x      6   DIO2_13  14  12
    149 */
    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 block
    159 * TS-5600 LCD port is identical
    160 *
    161 *  value    control  dir    hw
    162 *  addr bit addr bit in out irq name    pin offset
    163 *
    164 *  0x72  0  0x7d  2  x   x      LCD_0   8   0
    165 *  0x72  1  0x7d  2  x   x      LCD_1   7   1
    166 *  0x72  2  0x7d  2  x   x      LCD_2   10  2
    167 *  0x72  3  0x7d  2  x   x      LCD_3   9   3
    168 *  0x72  4  0x7d  3  x   x      LCD_4   12  4
    169 *  0x72  5  0x7d  3  x   x      LCD_5   11  5
    170 *  0x72  6  0x7d  3  x   x      LCD_6   14  6
    171 *  0x72  7  0x7d  3  x   x      LCD_7   13  7
    172 *  0x73  0               x      LCD_EN  5   8
    173 *  0x73  6           x          LCD_WR  6   9
    174 *  0x73  7           x      1   LCD_RS  3   10
    175 */
    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	else
    241		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	else
    257		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	else
    291		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	else
    309		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 int 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	return 0;
    422}
    423
    424static const struct platform_device_id ts5500_dio_ids[] = {
    425	{ "ts5500-dio1", TS5500_DIO1 },
    426	{ "ts5500-dio2", TS5500_DIO2 },
    427	{ "ts5500-dio-lcd", TS5500_LCD },
    428	{ "ts5600-dio-lcd", TS5600_LCD },
    429	{ }
    430};
    431MODULE_DEVICE_TABLE(platform, ts5500_dio_ids);
    432
    433static struct platform_driver ts5500_dio_driver = {
    434	.driver = {
    435		.name = "ts5500-dio",
    436	},
    437	.probe = ts5500_dio_probe,
    438	.remove = ts5500_dio_remove,
    439	.id_table = ts5500_dio_ids,
    440};
    441
    442module_platform_driver(ts5500_dio_driver);
    443
    444MODULE_LICENSE("GPL");
    445MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
    446MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver");