1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright 2008 Extreme Engineering Solutions, Inc.
4 */
5
6/*
7 * Driver for NXP's 4, 8 and 16 bit I2C gpio expanders (eg pca9537, pca9557,
8 * pca9539, etc)
9 */
10
11#include <config.h>
12#include <command.h>
13#include <i2c.h>
14#include <pca953x.h>
15#include <vsprintf.h>
16
17/* Default to an address that hopefully won't corrupt other i2c devices */
18#ifndef CFG_SYS_I2C_PCA953X_ADDR
19#define CFG_SYS_I2C_PCA953X_ADDR	(~0)
20#endif
21
22enum {
23	PCA953X_CMD_INFO,
24	PCA953X_CMD_DEVICE,
25	PCA953X_CMD_OUTPUT,
26	PCA953X_CMD_INPUT,
27	PCA953X_CMD_INVERT,
28};
29
30#ifdef CFG_SYS_I2C_PCA953X_WIDTH
31struct pca953x_chip_ngpio {
32	uint8_t chip;
33	uint8_t ngpio;
34};
35
36static struct pca953x_chip_ngpio pca953x_chip_ngpios[] =
37    CFG_SYS_I2C_PCA953X_WIDTH;
38
39/*
40 * Determine the number of GPIO pins supported. If we don't know we assume
41 * 8 pins.
42 */
43static int pca953x_ngpio(uint8_t chip)
44{
45	int i;
46
47	for (i = 0; i < ARRAY_SIZE(pca953x_chip_ngpios); i++)
48		if (pca953x_chip_ngpios[i].chip == chip)
49			return pca953x_chip_ngpios[i].ngpio;
50
51	return 8;
52}
53#else
54static int pca953x_ngpio(uint8_t chip)
55{
56	return 8;
57}
58#endif
59
60/*
61 * Modify masked bits in register
62 */
63static int pca953x_reg_write(uint8_t chip, uint addr, uint mask, uint data)
64{
65	uint8_t valb;
66	uint16_t valw;
67
68	if (pca953x_ngpio(chip) <= 8) {
69		if (i2c_read(chip, addr, 1, &valb, 1))
70			return -1;
71
72		valb &= ~mask;
73		valb |= data;
74
75		return i2c_write(chip, addr, 1, &valb, 1);
76	} else {
77		if (i2c_read(chip, addr << 1, 1, (u8*)&valw, 2))
78			return -1;
79
80		valw = le16_to_cpu(valw);
81		valw &= ~mask;
82		valw |= data;
83		valw = cpu_to_le16(valw);
84
85		return i2c_write(chip, addr << 1, 1, (u8*)&valw, 2);
86	}
87}
88
89static int pca953x_reg_read(uint8_t chip, uint addr, uint *data)
90{
91	uint8_t valb;
92	uint16_t valw;
93
94	if (pca953x_ngpio(chip) <= 8) {
95		if (i2c_read(chip, addr, 1, &valb, 1))
96			return -1;
97		*data = (int)valb;
98	} else {
99		if (i2c_read(chip, addr << 1, 1, (u8*)&valw, 2))
100			return -1;
101		*data = (uint)le16_to_cpu(valw);
102	}
103	return 0;
104}
105
106/*
107 * Set output value of IO pins in 'mask' to corresponding value in 'data'
108 * 0 = low, 1 = high
109 */
110int pca953x_set_val(uint8_t chip, uint mask, uint data)
111{
112	return pca953x_reg_write(chip, PCA953X_OUT, mask, data);
113}
114
115/*
116 * Set read polarity of IO pins in 'mask' to corresponding value in 'data'
117 * 0 = read pin value, 1 = read inverted pin value
118 */
119int pca953x_set_pol(uint8_t chip, uint mask, uint data)
120{
121	return pca953x_reg_write(chip, PCA953X_POL, mask, data);
122}
123
124/*
125 * Set direction of IO pins in 'mask' to corresponding value in 'data'
126 * 0 = output, 1 = input
127 */
128int pca953x_set_dir(uint8_t chip, uint mask, uint data)
129{
130	return pca953x_reg_write(chip, PCA953X_CONF, mask, data);
131}
132
133/*
134 * Read current logic level of all IO pins
135 */
136int pca953x_get_val(uint8_t chip)
137{
138	uint val;
139
140	if (pca953x_reg_read(chip, PCA953X_IN, &val) < 0)
141		return -1;
142
143	return (int)val;
144}
145
146#if defined(CONFIG_CMD_PCA953X) && !defined(CONFIG_SPL_BUILD)
147/*
148 * Display pca953x information
149 */
150static int pca953x_info(uint8_t chip)
151{
152	int i;
153	uint data;
154	int nr_gpio = pca953x_ngpio(chip);
155	int msb = nr_gpio - 1;
156
157	printf("pca953x@ 0x%x (%d pins):\n\n", chip, nr_gpio);
158	printf("gpio pins: ");
159	for (i = msb; i >= 0; i--)
160		printf("%x", i);
161	printf("\n");
162	for (i = 11 + nr_gpio; i > 0; i--)
163		printf("-");
164	printf("\n");
165
166	if (pca953x_reg_read(chip, PCA953X_CONF, &data) < 0)
167		return -1;
168	printf("conf:      ");
169	for (i = msb; i >= 0; i--)
170		printf("%c", data & (1 << i) ? 'i' : 'o');
171	printf("\n");
172
173	if (pca953x_reg_read(chip, PCA953X_POL, &data) < 0)
174		return -1;
175	printf("invert:    ");
176	for (i = msb; i >= 0; i--)
177		printf("%c", data & (1 << i) ? '1' : '0');
178	printf("\n");
179
180	if (pca953x_reg_read(chip, PCA953X_IN, &data) < 0)
181		return -1;
182	printf("input:     ");
183	for (i = msb; i >= 0; i--)
184		printf("%c", data & (1 << i) ? '1' : '0');
185	printf("\n");
186
187	if (pca953x_reg_read(chip, PCA953X_OUT, &data) < 0)
188		return -1;
189	printf("output:    ");
190	for (i = msb; i >= 0; i--)
191		printf("%c", data & (1 << i) ? '1' : '0');
192	printf("\n");
193
194	return 0;
195}
196
197static struct cmd_tbl cmd_pca953x[] = {
198	U_BOOT_CMD_MKENT(device, 3, 0, (void *)PCA953X_CMD_DEVICE, "", ""),
199	U_BOOT_CMD_MKENT(output, 4, 0, (void *)PCA953X_CMD_OUTPUT, "", ""),
200	U_BOOT_CMD_MKENT(input, 3, 0, (void *)PCA953X_CMD_INPUT, "", ""),
201	U_BOOT_CMD_MKENT(invert, 4, 0, (void *)PCA953X_CMD_INVERT, "", ""),
202	U_BOOT_CMD_MKENT(info, 2, 0, (void *)PCA953X_CMD_INFO, "", ""),
203};
204
205static int do_pca953x(struct cmd_tbl *cmdtp, int flag, int argc,
206		      char *const argv[])
207{
208	static uint8_t chip = CFG_SYS_I2C_PCA953X_ADDR;
209	int ret = CMD_RET_USAGE, val;
210	ulong ul_arg2 = 0;
211	ulong ul_arg3 = 0;
212	struct cmd_tbl *c;
213
214	c = find_cmd_tbl(argv[1], cmd_pca953x, ARRAY_SIZE(cmd_pca953x));
215
216	/* All commands but "device" require 'maxargs' arguments */
217	if (!c || !((argc == (c->maxargs)) ||
218		(((long)c->cmd == PCA953X_CMD_DEVICE) &&
219		 (argc == (c->maxargs - 1))))) {
220		return CMD_RET_USAGE;
221	}
222
223	/* arg2 used as chip number or pin number */
224	if (argc > 2)
225		ul_arg2 = hextoul(argv[2], NULL);
226
227	/* arg3 used as pin or invert value */
228	if (argc > 3)
229		ul_arg3 = hextoul(argv[3], NULL) & 0x1;
230
231	switch ((long)c->cmd) {
232	case PCA953X_CMD_INFO:
233		ret = pca953x_info(chip);
234		if (ret)
235			ret = CMD_RET_FAILURE;
236		break;
237
238	case PCA953X_CMD_DEVICE:
239		if (argc == 3)
240			chip = (uint8_t)ul_arg2;
241		printf("Current device address: 0x%x\n", chip);
242		ret = CMD_RET_SUCCESS;
243		break;
244
245	case PCA953X_CMD_INPUT:
246		ret = pca953x_set_dir(chip, (1 << ul_arg2),
247				PCA953X_DIR_IN << ul_arg2);
248		val = (pca953x_get_val(chip) & (1 << ul_arg2)) != 0;
249
250		if (ret)
251			ret = CMD_RET_FAILURE;
252		else
253			printf("chip 0x%02x, pin 0x%lx = %d\n", chip, ul_arg2,
254									val);
255		break;
256
257	case PCA953X_CMD_OUTPUT:
258		ret = pca953x_set_dir(chip, (1 << ul_arg2),
259				(PCA953X_DIR_OUT << ul_arg2));
260		if (!ret)
261			ret = pca953x_set_val(chip, (1 << ul_arg2),
262						(ul_arg3 << ul_arg2));
263		if (ret)
264			ret = CMD_RET_FAILURE;
265		break;
266
267	case PCA953X_CMD_INVERT:
268		ret = pca953x_set_pol(chip, (1 << ul_arg2),
269					(ul_arg3 << ul_arg2));
270		if (ret)
271			ret = CMD_RET_FAILURE;
272		break;
273	}
274
275	if (ret == CMD_RET_FAILURE)
276		eprintf("Error talking to chip at 0x%x\n", chip);
277
278	return ret;
279}
280
281U_BOOT_CMD(
282	pca953x,	5,	1,	do_pca953x,
283	"pca953x gpio access",
284	"device [dev]\n"
285	"	- show or set current device address\n"
286	"pca953x info\n"
287	"	- display info for current chip\n"
288	"pca953x output pin 0|1\n"
289	"	- set pin as output and drive low or high\n"
290	"pca953x invert pin 0|1\n"
291	"	- disable/enable polarity inversion for reads\n"
292	"pca953x input pin\n"
293	"	- set pin as input and read value"
294);
295
296#endif /* CONFIG_CMD_PCA953X */
297