aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ath79/files/drivers/mfd/rb4xx-cpld.c
blob: da18424c63383c563777e33fe156ceb5fdb8a082 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-License-Identifier: GPL-2.0-only
/*
 * CPLD driver for the MikroTik RouterBoard 4xx series
 *
 * This driver provides access to a CPLD that interfaces between the SoC SPI bus
 * and other devices. Behind the CPLD there is a NAND flash chip and five LEDs.
 *
 * The CPLD supports SPI two-wire mode, in which two bits are transferred per
 * SPI clock cycle. The second bit is transmitted with the SoC's CS2 pin.
 *
 * The CPLD also acts as a GPIO expander.
 *
 * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
 * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
 * Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
 * Copyright (C) 2020 Christopher Hill <ch6574@gmail.com>
 *
 * This file was based on the driver for Linux 2.6.22 published by
 * MikroTik for their RouterBoard 4xx series devices.
*/
#include <linux/mfd/core.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/of_platform.h>

#include <mfd/rb4xx-cpld.h>

/* CPLD commands */
#define CPLD_CMD_WRITE_NAND	0x08 /* send cmd, n x send data, send idle */
#define CPLD_CMD_WRITE_CFG	0x09 /* send cmd, n x send cfg */
#define CPLD_CMD_READ_NAND	0x0a /* send cmd, send idle, n x read data */
#define CPLD_CMD_READ_FAST	0x0b /* send cmd, 4 x idle, n x read data */
#define CPLD_CMD_GPIO8_HIGH	0x0c /* send cmd */
#define CPLD_CMD_GPIO8_LOW	0x0d /* send cmd */

static int rb4xx_cpld_write_nand(struct rb4xx_cpld *cpld, const void *tx_buf,
				 unsigned int len)
{
	struct spi_message m;
	static const u8 cmd = CPLD_CMD_WRITE_NAND;
	struct spi_transfer t[3] = {
		{
			.tx_buf = &cmd,
			.len = sizeof(cmd),
		}, {
			.tx_buf = tx_buf,
			.len = len,
			.tx_nbits = SPI_NBITS_DUAL,
		}, {
			.len = 1,
			.tx_nbits = SPI_NBITS_DUAL,
		},
	};

	spi_message_init(&m);
	spi_message_add_tail(&t[0], &m);
	spi_message_add_tail(&t[1], &m);
	spi_message_add_tail(&t[2], &m);
	return spi_sync(cpld->spi, &m);
}

static int rb4xx_cpld_read_nand(struct rb4xx_cpld *cpld, void *rx_buf,
				unsigned int len)
{
	struct spi_message m;
	static const u8 cmd[2] = {
		CPLD_CMD_READ_NAND, 0
	};
	struct spi_transfer t[2] = {
		{
			.tx_buf = &cmd,
			.len = sizeof(cmd),
		}, {
			.rx_buf = rx_buf,
			.len = len,
		},
	};

	spi_message_init(&m);
	spi_message_add_tail(&t[0], &m);
	spi_message_add_tail(&t[1], &m);
	return spi_sync(cpld->spi, &m);
}

static int rb4xx_cpld_cmd(struct rb4xx_cpld *cpld, const void *tx_buf,
			  unsigned int len)
{
	struct spi_message m;
	struct spi_transfer t = {
		.tx_buf = tx_buf,
		.len = len,
	};

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spi_sync(cpld->spi, &m);
}

static int rb4xx_cpld_gpio_set_0_7(struct rb4xx_cpld *cpld, u8 values)
{
	/* GPIO 0-7 change can be sent via command + bitfield */
	u8 cmd[2] = {
		CPLD_CMD_WRITE_CFG, values
	};
	return rb4xx_cpld_cmd(cpld, &cmd, 2);
}

static int rb4xx_cpld_gpio_set_8(struct rb4xx_cpld *cpld, u8 value)
{
	/* GPIO 8 uses dedicated high/low commands */
	u8 cmd = CPLD_CMD_GPIO8_HIGH | !!(value);
	return rb4xx_cpld_cmd(cpld, &cmd, 1);
}

static const struct mfd_cell rb4xx_cpld_cells[] = {
	{
		.name = "mikrotik,rb4xx-gpio",
		.of_compatible = "mikrotik,rb4xx-gpio",
	}, {
		.name = "mikrotik,rb4xx-nand",
		.of_compatible = "mikrotik,rb4xx-nand",
	},
};

static int rb4xx_cpld_probe(struct spi_device *spi)
{
	struct device *dev = &spi->dev;
	struct rb4xx_cpld *cpld;
	int ret;

	cpld = devm_kzalloc(dev, sizeof(*cpld), GFP_KERNEL);
	if (!cpld)
		return -ENOMEM;

	dev_set_drvdata(dev, cpld);

	cpld->spi		= spi;
	cpld->write_nand	= rb4xx_cpld_write_nand;
	cpld->read_nand		= rb4xx_cpld_read_nand;
	cpld->gpio_set_0_7	= rb4xx_cpld_gpio_set_0_7;
	cpld->gpio_set_8	= rb4xx_cpld_gpio_set_8;

	spi->mode = SPI_MODE_0 | SPI_TX_DUAL;
	ret = spi_setup(spi);
	if (ret)
		return ret;

	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
				    rb4xx_cpld_cells,
				    ARRAY_SIZE(rb4xx_cpld_cells),
				    NULL, 0, NULL);
}

static int rb4xx_cpld_remove(struct spi_device *spi)
{
	return 0;
}

static const struct of_device_id rb4xx_cpld_dt_match[] = {
	{ .compatible = "mikrotik,rb4xx-cpld", },
	{ },
};
MODULE_DEVICE_TABLE(of, rb4xx_cpld_dt_match);

static struct spi_driver rb4xx_cpld_driver = {
	.probe = rb4xx_cpld_probe,
	.remove = rb4xx_cpld_remove,
	.driver = {
		.name = "rb4xx-cpld",
		.bus = &spi_bus_type,
		.of_match_table = of_match_ptr(rb4xx_cpld_dt_match),
	},
};

module_spi_driver(rb4xx_cpld_driver);

MODULE_DESCRIPTION("Mikrotik RB4xx CPLD driver");
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org>");
MODULE_AUTHOR("Bert Vermeulen <bert@biot.com>");
MODULE_AUTHOR("Christopher Hill <ch6574@gmail.com");
MODULE_LICENSE("GPL v2");