summaryrefslogtreecommitdiff
path: root/driver/ioexpander/tca64xxa.c
blob: 9a70ceec11ad3898ee49c444eef482b9ebbe9605 (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "common.h"
#include "i2c.h"
#include "ioexpander.h"
#include "system.h"
#include "tca64xxa.h"

/*
 * This chip series contain registers in the same order.
 * Difference between models is only amount of registers and
 * value of which you must multiply to access specific register.
 * For 16 bit series, registers are 2 byte away, so to access TCA64XXA_REG_CONF
 * you must multiply it by 2. For 24 bit, they are away by 4 bytes so you
 * must multiply them by 4. Flags value contains information which version
 * of chip is used.
 */
#define TCA64XXA_PORT_ID(port, reg, flags) \
	((((flags) & TCA64XXA_FLAG_VER_MASK) \
	>> TCA64XXA_FLAG_VER_OFFSET) * (reg) + (port))

static int tca64xxa_write_byte(int ioex, int port, int reg, uint8_t val)
{
	const struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
	const int reg_addr = TCA64XXA_PORT_ID(port, reg, ioex_p->flags);

	return i2c_write8(ioex_p->i2c_host_port,
			  ioex_p->i2c_addr_flags,
			  reg_addr,
			  val);
}

static int tca64xxa_read_byte(int ioex, int port, int reg, int *val)
{
	const struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
	const int reg_addr = TCA64XXA_PORT_ID(port, reg, ioex_p->flags);

	return i2c_read8(ioex_p->i2c_host_port,
			 ioex_p->i2c_addr_flags,
			 reg_addr,
			 val);
}

/* Restore default values in registers */
static int tca64xxa_reset(int ioex, int portsCount)
{
	int port;
	int ret;

	/*
	 * On servo_v4p1, reset pin is pulled up and it results in values
	 * not being restored to default ones after software reboot.
	 * This loop sets default values (from specification) to all registers.
	 */
	for (port = 0; port < portsCount; port++) {
		ret = tca64xxa_write_byte(ioex,
					  port,
					  TCA64XXA_REG_OUTPUT,
					  TCA64XXA_DEFAULT_OUTPUT);
		if (ret)
			return ret;

		ret = tca64xxa_write_byte(ioex,
					  port,
					  TCA64XXA_REG_POLARITY_INV,
					  TCA64XXA_DEFAULT_POLARITY_INV);
		if (ret)
			return ret;

		ret = tca64xxa_write_byte(ioex,
					  port,
					  TCA64XXA_REG_CONF,
					  TCA64XXA_DEFAULT_CONF);
		if (ret)
			return ret;
	}

	return EC_SUCCESS;
}

/* Initialize IO expander chip/driver */
static int tca64xxa_init(int ioex)
{
	const struct ioexpander_config_t *ioex_p = &ioex_config[ioex];
	int portsCount;

	if (ioex_p->flags & TCA64XXA_FLAG_VER_TCA6416A)
		portsCount = 2;
	else if (ioex_p->flags & TCA64XXA_FLAG_VER_TCA6424A)
		portsCount = 3;
	else
		return EC_ERROR_UNIMPLEMENTED;

	if (!system_jumped_late())
		return tca64xxa_reset(ioex, portsCount);

	return EC_SUCCESS;
}

/* Get the current level of the IOEX pin */
static int tca64xxa_get_level(int ioex, int port, int mask, int *val)
{
	int buf;
	int ret;

	ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_INPUT, &buf);
	*val = !!(buf & mask);

	return ret;
}

/* Set the level of the IOEX pin */
static int tca64xxa_set_level(int ioex, int port, int mask, int val)
{
	int ret;
	int v;

	ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_OUTPUT, &v);
	if (ret)
		return ret;

	if (val)
		v |= mask;
	else
		v &= ~mask;

	return tca64xxa_write_byte(ioex, port, TCA64XXA_REG_OUTPUT, v);
}

/* Get flags for the IOEX pin */
static int tca64xxa_get_flags_by_mask(int ioex, int port, int mask, int *flags)
{
	int ret;
	int v;

	ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_CONF, &v);
	if (ret)
		return ret;

	*flags = 0;
	if (v & mask) {
		*flags |= GPIO_INPUT;
	} else {
		*flags |= GPIO_OUTPUT;

		ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_OUTPUT, &v);
		if(ret)
			return ret;

		if (v & mask)
			*flags |= GPIO_HIGH;
		else
			*flags |= GPIO_LOW;
	}

	return EC_SUCCESS;
}

/* Set flags for the IOEX pin */
static int tca64xxa_set_flags_by_mask(int ioex, int port, int mask, int flags)
{
	int ret;
	int v;

	/* Output value */
	if (flags & GPIO_OUTPUT) {
		ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_OUTPUT, &v);
		if (ret)
			return ret;

		if (flags & GPIO_LOW)
			v &= ~mask;
		else if (flags & GPIO_HIGH)
			v |= mask;
		else
			return EC_ERROR_INVAL;

		ret = tca64xxa_write_byte(ioex, port, TCA64XXA_REG_OUTPUT, v);
		if (ret)
			return ret;
	}

	/* Configuration */
	ret = tca64xxa_read_byte(ioex, port, TCA64XXA_REG_CONF, &v);
	if(ret)
		return ret;

	if (flags & GPIO_INPUT)
		v |= mask;
	else if (flags & GPIO_OUTPUT)
		v &= ~mask;
	else
		return EC_ERROR_INVAL;

	ret = tca64xxa_write_byte(ioex, port, TCA64XXA_REG_CONF, v);
	if (ret)
		return ret;

	return EC_SUCCESS;
}

#ifdef CONFIG_IO_EXPANDER_SUPPORT_GET_PORT

/* Read levels for whole IO expander port */
static int tca64xxa_get_port(int ioex, int port, int *val)
{
	return tca64xxa_read_byte(ioex, port, TCA64XXA_REG_INPUT, val);
}

#endif

/* Driver structure */
const struct ioexpander_drv tca64xxa_ioexpander_drv = {
	.init 			= tca64xxa_init,
	.get_level 		= tca64xxa_get_level,
	.set_level 		= tca64xxa_set_level,
	.get_flags_by_mask 	= tca64xxa_get_flags_by_mask,
	.set_flags_by_mask 	= tca64xxa_set_flags_by_mask,
	.enable_interrupt 	= NULL,
#ifdef CONFIG_IO_EXPANDER_SUPPORT_GET_PORT
	.get_port		= tca64xxa_get_port,
#endif
};