Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 : /*
3 : * Copyright (C) 2021 Steven Stallion <sstallion@gmail.com>
4 : *
5 : * This library is free software; you can redistribute it and/or modify
6 : * it under the terms of the GNU Lesser General Public License as published
7 : * by the Free Software Foundation; either version 2.1 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This library is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
13 : * the GNU Lesser General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public License
16 : * along with this library; if not, see <http://www.gnu.org/licenses/>.
17 : */
18 :
19 : #include "mcp23016-private.h"
20 :
21 : #include <assert.h>
22 : #include <endian.h>
23 : #include <errno.h>
24 : #include <stdint.h>
25 : #include <stdlib.h>
26 : #include <gpiod.h>
27 : #include <i2cd.h>
28 :
29 4 : struct mcp23016_device *mcp23016_open(const char *path, unsigned int num)
30 : {
31 : struct mcp23016_device *dev;
32 :
33 : assert(path != NULL);
34 :
35 4 : dev = calloc(1, sizeof(*dev));
36 4 : if (dev == NULL)
37 1 : return NULL;
38 :
39 3 : dev->i2c_addr = BASE_ADDR + num;
40 3 : if (dev->i2c_addr < BASE_ADDR || dev->i2c_addr > END_ADDR) {
41 1 : errno = EINVAL;
42 1 : goto err;
43 : }
44 :
45 2 : dev->i2c_dev = i2cd_open(path);
46 2 : if (dev->i2c_dev == NULL)
47 1 : goto err;
48 :
49 1 : return dev;
50 2 : err:
51 2 : free(dev);
52 2 : return NULL;
53 : }
54 :
55 1 : void mcp23016_close(struct mcp23016_device *dev)
56 : {
57 : assert(dev != NULL);
58 :
59 1 : i2cd_close(dev->i2c_dev);
60 :
61 1 : free(dev);
62 1 : }
63 :
64 1 : int mcp23016_reset(struct mcp23016_device *dev)
65 : {
66 : int res;
67 :
68 : assert(dev != NULL);
69 :
70 : /* The MCP23016 does not provide a hardware reset. The following
71 : * sequence resets registers to POR defaults and clears pending
72 : * interrupts.
73 : */
74 1 : res = mcp23016_set_direction(dev, 0xffff);
75 1 : if (res < 0)
76 0 : return res;
77 :
78 1 : res = mcp23016_set_output(dev, 0x0000);
79 1 : if (res < 0)
80 0 : return res;
81 :
82 1 : res = mcp23016_set_polarity(dev, 0x0000);
83 1 : if (res < 0)
84 0 : return res;
85 :
86 1 : res = mcp23016_set_control(dev, 0x0000);
87 1 : if (res < 0)
88 0 : return res;
89 :
90 1 : return mcp23016_clear_interrupt(dev);
91 : }
92 :
93 7 : int mcp23016_register_read(struct mcp23016_device *dev, uint8_t reg, uint16_t *val)
94 : {
95 : int res;
96 :
97 : assert(dev != NULL);
98 : assert(val != NULL);
99 :
100 : /* 16-bit registers are accessed by reading an additional byte.
101 : * Values are encoded in little-endian byte order.
102 : */
103 7 : res = i2cd_register_read(dev->i2c_dev, dev->i2c_addr, reg, val, sizeof(*val));
104 7 : if (res < 0)
105 0 : return res;
106 :
107 7 : *val = le16toh(*val);
108 7 : return 0;
109 : }
110 :
111 9 : int mcp23016_register_write(struct mcp23016_device *dev, uint8_t reg, uint16_t val)
112 : {
113 9 : const uint8_t buf[] = {reg, LOW(val), HIGH(val)};
114 : int res;
115 :
116 : assert(dev != NULL);
117 :
118 : /* 16-bit registers are accessed by writing an additional byte.
119 : * Values are encoded in little-endian byte order.
120 : */
121 9 : res = i2cd_write(dev->i2c_dev, dev->i2c_addr, buf, sizeof(buf));
122 9 : if (res < 0)
123 0 : return res;
124 :
125 9 : return 0;
126 : }
127 :
128 1 : int mcp23016_get_port(struct mcp23016_device *dev, uint16_t *val)
129 : {
130 1 : return mcp23016_register_read(dev, REG_GP0, val);
131 : }
132 :
133 1 : int mcp23016_set_port(struct mcp23016_device *dev, uint16_t val)
134 : {
135 1 : return mcp23016_register_write(dev, REG_GP0, val);
136 : }
137 :
138 1 : int mcp23016_get_output(struct mcp23016_device *dev, uint16_t *val)
139 : {
140 1 : return mcp23016_register_read(dev, REG_OLAT0, val);
141 : }
142 :
143 2 : int mcp23016_set_output(struct mcp23016_device *dev, uint16_t val)
144 : {
145 2 : return mcp23016_register_write(dev, REG_OLAT0, val);
146 : }
147 :
148 1 : int mcp23016_get_polarity(struct mcp23016_device *dev, uint16_t *val)
149 : {
150 1 : return mcp23016_register_read(dev, REG_IPOL0, val);
151 : }
152 :
153 2 : int mcp23016_set_polarity(struct mcp23016_device *dev, uint16_t val)
154 : {
155 2 : return mcp23016_register_write(dev, REG_IPOL0, val);
156 : }
157 :
158 1 : int mcp23016_get_direction(struct mcp23016_device *dev, uint16_t *val)
159 : {
160 1 : return mcp23016_register_read(dev, REG_IODIR0, val);
161 : }
162 :
163 2 : int mcp23016_set_direction(struct mcp23016_device *dev, uint16_t val)
164 : {
165 2 : return mcp23016_register_write(dev, REG_IODIR0, val);
166 : }
167 :
168 2 : int mcp23016_get_interrupt(struct mcp23016_device *dev, uint16_t *val)
169 : {
170 2 : return mcp23016_register_read(dev, REG_INTCAP0, val);
171 : }
172 :
173 1 : int mcp23016_get_control(struct mcp23016_device *dev, uint16_t *val)
174 : {
175 1 : return mcp23016_register_read(dev, REG_IOCON0, val);
176 : }
177 :
178 2 : int mcp23016_set_control(struct mcp23016_device *dev, uint16_t val)
179 : {
180 2 : return mcp23016_register_write(dev, REG_IOCON0, val);
181 : }
182 :
183 5 : struct mcp23016_interrupt *mcp23016_interrupt_open(const char *path, unsigned int offset)
184 : {
185 : struct mcp23016_interrupt *intr;
186 : int flags, errsv;
187 :
188 : assert(path != NULL);
189 :
190 5 : intr = calloc(1, sizeof(*intr));
191 5 : if (intr == NULL)
192 1 : return NULL;
193 :
194 4 : intr->gpio_chip = gpiod_chip_open(path);
195 4 : if (intr->gpio_chip == NULL)
196 1 : goto err;
197 :
198 3 : intr->gpio_line = gpiod_chip_get_line(intr->gpio_chip, offset);
199 3 : if (intr->gpio_line == NULL)
200 1 : goto err;
201 :
202 2 : flags = GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
203 2 : if (gpiod_line_request_input_flags(intr->gpio_line, CONSUMER, flags) < 0)
204 1 : goto err;
205 :
206 1 : return intr;
207 3 : err:
208 3 : errsv = errno;
209 :
210 3 : if (intr->gpio_line != NULL)
211 1 : gpiod_line_release(intr->gpio_line);
212 :
213 3 : if (intr->gpio_chip != NULL)
214 2 : gpiod_chip_close(intr->gpio_chip);
215 :
216 3 : free(intr);
217 :
218 3 : errno = errsv;
219 3 : return NULL;
220 : }
221 :
222 1 : void mcp23016_interrupt_close(struct mcp23016_interrupt *intr)
223 : {
224 : assert(intr != NULL);
225 :
226 1 : gpiod_line_release(intr->gpio_line);
227 1 : gpiod_chip_close(intr->gpio_chip);
228 :
229 1 : free(intr);
230 1 : }
231 :
232 1 : int mcp23016_has_interrupt(struct mcp23016_interrupt *intr)
233 : {
234 1 : return gpiod_line_get_value(intr->gpio_line);
235 : }
|