pcf8574.c revision 1.7 1 1.7 thorpej /* $NetBSD: pcf8574.c,v 1.7 2021/01/17 21:42:35 thorpej Exp $ */
2 1.3 jdc
3 1.1 jdc /*-
4 1.1 jdc * Copyright (c) 2020 The NetBSD Foundation, Inc.
5 1.1 jdc * All rights reserved.
6 1.1 jdc *
7 1.1 jdc * This code is derived from software contributed to The NetBSD Foundation
8 1.1 jdc * by Julian Coleman.
9 1.1 jdc *
10 1.1 jdc * Redistribution and use in source and binary forms, with or without
11 1.1 jdc * modification, are permitted provided that the following conditions
12 1.1 jdc * are met:
13 1.1 jdc * 1. Redistributions of source code must retain the above copyright
14 1.1 jdc * notice, this list of conditions and the following disclaimer.
15 1.1 jdc * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 jdc * notice, this list of conditions and the following disclaimer in the
17 1.1 jdc * documentation and/or other materials provided with the distribution.
18 1.1 jdc *
19 1.1 jdc * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 1.1 jdc * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 1.1 jdc * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 1.1 jdc * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 1.1 jdc * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 1.1 jdc * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 1.1 jdc * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 1.1 jdc * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 1.1 jdc * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 1.1 jdc * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 1.1 jdc * POSSIBILITY OF SUCH DAMAGE.
30 1.1 jdc */
31 1.1 jdc
32 1.1 jdc /*
33 1.1 jdc * A driver for Philips Semiconductor (NXP) PCF8574/PCF857A GPIO's.
34 1.1 jdc * Uses device properties to connect pins to the appropriate subsystem.
35 1.1 jdc */
36 1.1 jdc
37 1.1 jdc #include <sys/cdefs.h>
38 1.7 thorpej __KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.7 2021/01/17 21:42:35 thorpej Exp $");
39 1.1 jdc
40 1.1 jdc #include <sys/param.h>
41 1.1 jdc #include <sys/systm.h>
42 1.1 jdc #include <sys/device.h>
43 1.1 jdc #include <sys/kernel.h>
44 1.1 jdc
45 1.1 jdc #include <dev/sysmon/sysmonvar.h>
46 1.6 jdc #include <dev/sysmon/sysmon_taskq.h>
47 1.1 jdc
48 1.1 jdc #include <dev/i2c/i2cvar.h>
49 1.1 jdc #include <dev/led.h>
50 1.1 jdc
51 1.1 jdc #ifdef PCF8574_DEBUG
52 1.1 jdc #define DPRINTF printf
53 1.1 jdc #else
54 1.1 jdc #define DPRINTF if (0) printf
55 1.1 jdc #endif
56 1.1 jdc
57 1.1 jdc struct pcf8574_led {
58 1.1 jdc void *cookie;
59 1.2 jdc struct led_device *led;
60 1.1 jdc uint8_t mask, v_on, v_off;
61 1.1 jdc };
62 1.1 jdc
63 1.6 jdc struct pcf8574_pin {
64 1.6 jdc int pin_sensor;
65 1.6 jdc int pin_active;
66 1.6 jdc char pin_desc[ENVSYS_DESCLEN];
67 1.6 jdc };
68 1.6 jdc
69 1.1 jdc #define PCF8574_NPINS 8
70 1.1 jdc struct pcf8574_softc {
71 1.1 jdc device_t sc_dev;
72 1.1 jdc i2c_tag_t sc_tag;
73 1.1 jdc i2c_addr_t sc_addr;
74 1.1 jdc uint8_t sc_state;
75 1.1 jdc uint8_t sc_mask;
76 1.1 jdc
77 1.6 jdc uint8_t sc_alert_mask;
78 1.6 jdc #define PCF8574_DEFAULT_TIMER 60
79 1.6 jdc int sc_callout_time;
80 1.6 jdc callout_t sc_timer;
81 1.6 jdc
82 1.1 jdc int sc_nleds;
83 1.1 jdc struct pcf8574_led sc_leds[PCF8574_NPINS];
84 1.6 jdc struct pcf8574_pin sc_pins[PCF8574_NPINS];
85 1.1 jdc
86 1.1 jdc struct sysmon_envsys *sc_sme;
87 1.1 jdc envsys_data_t sc_sensor[PCF8574_NPINS];
88 1.1 jdc };
89 1.1 jdc
90 1.1 jdc static int pcf8574_match(device_t, cfdata_t, void *);
91 1.1 jdc static void pcf8574_attach(device_t, device_t, void *);
92 1.1 jdc static int pcf8574_detach(device_t, int);
93 1.1 jdc
94 1.6 jdc static int pcf8574_read(struct pcf8574_softc *sc, uint8_t *);
95 1.6 jdc static int pcf8574_write(struct pcf8574_softc *sc, uint8_t);
96 1.1 jdc static void pcf8574_attach_led(
97 1.1 jdc struct pcf8574_softc *, char *, int, int, int);
98 1.6 jdc static int pcf8574_attach_sysmon(
99 1.6 jdc struct pcf8574_softc *, char *, int, int, int);
100 1.1 jdc void pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *);
101 1.1 jdc int pcf8574_get_led(void *);
102 1.1 jdc void pcf8574_set_led(void *, int);
103 1.6 jdc static void pcf8574_timeout(void *);
104 1.6 jdc static void pcf8574_check(void *);
105 1.6 jdc static void pcf8574_check_alert(struct pcf8574_softc *, uint8_t, uint8_t);
106 1.1 jdc
107 1.1 jdc CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc),
108 1.1 jdc pcf8574_match, pcf8574_attach, pcf8574_detach, NULL);
109 1.1 jdc
110 1.1 jdc static const struct device_compatible_entry compat_data[] = {
111 1.7 thorpej { .compat = "i2c-pcf8574" },
112 1.7 thorpej
113 1.7 thorpej { 0 }
114 1.1 jdc };
115 1.1 jdc
116 1.1 jdc static int
117 1.1 jdc pcf8574_match(device_t parent, cfdata_t cf, void *aux)
118 1.1 jdc {
119 1.1 jdc struct i2c_attach_args *ia = aux;
120 1.1 jdc int match_result;
121 1.1 jdc
122 1.5 jdc if (iic_use_direct_match(ia, cf, compat_data, &match_result))
123 1.5 jdc return match_result;
124 1.1 jdc
125 1.1 jdc /* We don't support indirect matches */
126 1.1 jdc return 0;
127 1.1 jdc }
128 1.1 jdc
129 1.1 jdc static void
130 1.1 jdc pcf8574_attach(device_t parent, device_t self, void *aux)
131 1.1 jdc {
132 1.1 jdc struct pcf8574_softc *sc = device_private(self);
133 1.1 jdc struct i2c_attach_args *ia = aux;
134 1.1 jdc prop_dictionary_t dict = device_properties(self);
135 1.1 jdc prop_array_t pins;
136 1.1 jdc prop_dictionary_t pin;
137 1.1 jdc int i, num, def, envc = 0;
138 1.1 jdc char name[32];
139 1.1 jdc const char *nptr = NULL, *spptr;
140 1.6 jdc bool ok = TRUE, act;
141 1.1 jdc
142 1.1 jdc sc->sc_tag = ia->ia_tag;
143 1.1 jdc sc->sc_addr = ia->ia_addr;
144 1.1 jdc sc->sc_dev = self;
145 1.1 jdc
146 1.6 jdc sc->sc_sme = NULL;
147 1.6 jdc #ifdef PCF8574_DEBUG
148 1.6 jdc sc->sc_callout_time = 60; /* watch for changes when debugging */
149 1.6 jdc #else
150 1.6 jdc sc->sc_callout_time = 0;
151 1.6 jdc #endif
152 1.6 jdc
153 1.1 jdc /*
154 1.1 jdc * The PCF8574 requires input pins to be written with the value 1,
155 1.1 jdc * and then read. Assume that all pins are input initially.
156 1.1 jdc * We'll use the mask when we write, because we have to write a
157 1.1 jdc * value for every pin, and we don't want to change input pins.
158 1.1 jdc */
159 1.1 jdc sc->sc_mask = 0xff;
160 1.1 jdc
161 1.5 jdc /* Try a read, and fail if this component isn't present */
162 1.5 jdc if (pcf8574_read(sc, &sc->sc_state)) {
163 1.5 jdc aprint_normal(": read failed\n");
164 1.5 jdc return;
165 1.5 jdc }
166 1.1 jdc
167 1.1 jdc #ifdef PCF8574_DEBUG
168 1.1 jdc aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state);
169 1.1 jdc #else
170 1.1 jdc aprint_normal(": GPIO\n");
171 1.1 jdc #endif
172 1.1 jdc
173 1.1 jdc pins = prop_dictionary_get(dict, "pins");
174 1.1 jdc if (pins == NULL)
175 1.1 jdc return;
176 1.1 jdc
177 1.1 jdc for (i = 0; i < prop_array_count(pins); i++) {
178 1.1 jdc pin = prop_array_get(pins, i);
179 1.1 jdc ok &= prop_dictionary_get_cstring_nocopy(pin, "name", &nptr);
180 1.1 jdc ok &= prop_dictionary_get_uint32(pin, "pin", &num);
181 1.1 jdc ok &= prop_dictionary_get_bool(pin, "active_high", &act);
182 1.1 jdc /* optional default state */
183 1.1 jdc def = -1;
184 1.1 jdc prop_dictionary_get_int32(pin, "default_state", &def);
185 1.1 jdc if (!ok)
186 1.1 jdc continue;
187 1.1 jdc /* Extract pin type from the name */
188 1.1 jdc spptr = strstr(nptr, " ");
189 1.1 jdc if (spptr == NULL)
190 1.1 jdc continue;
191 1.1 jdc spptr += 1;
192 1.1 jdc strncpy(name, spptr, 31);
193 1.6 jdc sc->sc_pins[i].pin_active = act;
194 1.1 jdc if (!strncmp(nptr, "LED ", 4)) {
195 1.1 jdc sc->sc_mask &= ~(1 << num);
196 1.1 jdc pcf8574_attach_led(sc, name, num, act, def);
197 1.1 jdc }
198 1.6 jdc if (!strncmp(nptr, "INDICATOR ", 10)) {
199 1.6 jdc if (pcf8574_attach_sysmon(sc, name, envc, num, act))
200 1.6 jdc return;
201 1.6 jdc envc++;
202 1.6 jdc }
203 1.6 jdc if (!strncmp(nptr, "ALERT ", 6)) {
204 1.6 jdc u_int64_t alert_time;
205 1.6 jdc
206 1.6 jdc if (pcf8574_attach_sysmon(sc, name, envc, num, act))
207 1.1 jdc return;
208 1.6 jdc
209 1.6 jdc /* Adjust our timeout if the alert times out. */
210 1.6 jdc if (def != -1)
211 1.6 jdc alert_time = def / 2;
212 1.6 jdc else
213 1.6 jdc alert_time = PCF8574_DEFAULT_TIMER;
214 1.6 jdc
215 1.6 jdc if (sc->sc_callout_time == 0 ||
216 1.6 jdc alert_time < sc->sc_callout_time)
217 1.6 jdc sc->sc_callout_time = alert_time;
218 1.6 jdc
219 1.6 jdc sc->sc_alert_mask |= (1 << num);
220 1.1 jdc envc++;
221 1.1 jdc }
222 1.1 jdc }
223 1.1 jdc
224 1.6 jdc if (sc->sc_sme != NULL) {
225 1.1 jdc sc->sc_sme->sme_name = device_xname(self);
226 1.1 jdc sc->sc_sme->sme_cookie = sc;
227 1.1 jdc sc->sc_sme->sme_refresh = pcf8574_refresh;
228 1.1 jdc if (sysmon_envsys_register(sc->sc_sme)) {
229 1.1 jdc aprint_error_dev(self,
230 1.1 jdc "unable to register with sysmon\n");
231 1.1 jdc sysmon_envsys_destroy(sc->sc_sme);
232 1.3 jdc sc->sc_sme = NULL;
233 1.1 jdc return;
234 1.1 jdc }
235 1.1 jdc }
236 1.6 jdc
237 1.6 jdc if (sc->sc_callout_time) {
238 1.6 jdc callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
239 1.6 jdc callout_reset(&sc->sc_timer, hz * sc->sc_callout_time,
240 1.6 jdc pcf8574_timeout, sc);
241 1.6 jdc }
242 1.1 jdc }
243 1.1 jdc
244 1.1 jdc static int
245 1.1 jdc pcf8574_detach(device_t self, int flags)
246 1.1 jdc {
247 1.1 jdc struct pcf8574_softc *sc = device_private(self);
248 1.2 jdc int i;
249 1.2 jdc
250 1.6 jdc if (sc->sc_callout_time) {
251 1.6 jdc callout_halt(&sc->sc_timer, NULL);
252 1.6 jdc callout_destroy(&sc->sc_timer);
253 1.6 jdc sc->sc_callout_time = 0;
254 1.6 jdc }
255 1.6 jdc
256 1.3 jdc if (sc->sc_sme != NULL) {
257 1.2 jdc sysmon_envsys_unregister(sc->sc_sme);
258 1.3 jdc sc->sc_sme = NULL;
259 1.3 jdc }
260 1.2 jdc
261 1.2 jdc for (i = 0; i < sc->sc_nleds; i++)
262 1.2 jdc led_detach(sc->sc_leds[i].led);
263 1.1 jdc
264 1.1 jdc return 0;
265 1.1 jdc }
266 1.1 jdc
267 1.1 jdc static int
268 1.1 jdc pcf8574_read(struct pcf8574_softc *sc, uint8_t *val)
269 1.1 jdc {
270 1.1 jdc int err = 0;
271 1.1 jdc
272 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
273 1.1 jdc return err;
274 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
275 1.1 jdc sc->sc_addr, NULL, 0, val, 1, 0);
276 1.1 jdc iic_release_bus(sc->sc_tag, 0);
277 1.1 jdc return err;
278 1.1 jdc }
279 1.1 jdc
280 1.1 jdc static int
281 1.1 jdc pcf8574_write(struct pcf8574_softc *sc, uint8_t val)
282 1.1 jdc {
283 1.1 jdc int err = 0;
284 1.1 jdc
285 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
286 1.1 jdc return err;
287 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
288 1.1 jdc &val, 1, NULL, 0, 0);
289 1.1 jdc iic_release_bus(sc->sc_tag, 0);
290 1.1 jdc return err;
291 1.1 jdc }
292 1.1 jdc
293 1.1 jdc static void
294 1.6 jdc pcf8574_attach_led(struct pcf8574_softc *sc, char *name, int pin, int act,
295 1.6 jdc int def)
296 1.1 jdc {
297 1.1 jdc struct pcf8574_led *l;
298 1.1 jdc
299 1.1 jdc l = &sc->sc_leds[sc->sc_nleds];
300 1.1 jdc l->cookie = sc;
301 1.1 jdc l->mask = 1 << pin;
302 1.1 jdc l->v_on = act ? l->mask : 0;
303 1.1 jdc l->v_off = act ? 0 : l->mask;
304 1.6 jdc led_attach(name, l, pcf8574_get_led, pcf8574_set_led);
305 1.1 jdc if (def != -1)
306 1.1 jdc pcf8574_set_led(l, def);
307 1.1 jdc DPRINTF("%s: attached LED: %02x %02x %02x def %d\n",
308 1.1 jdc device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def);
309 1.1 jdc sc->sc_nleds++;
310 1.1 jdc }
311 1.1 jdc
312 1.6 jdc static int
313 1.6 jdc pcf8574_attach_sysmon(struct pcf8574_softc *sc, char *name, int envc, int pin,
314 1.6 jdc int act)
315 1.6 jdc {
316 1.6 jdc int ret;
317 1.6 jdc
318 1.6 jdc if (sc->sc_sme == NULL) {
319 1.6 jdc sc->sc_sme = sysmon_envsys_create();
320 1.6 jdc sc->sc_sme->sme_events_timeout = 0;
321 1.6 jdc }
322 1.6 jdc
323 1.6 jdc strlcpy(sc->sc_pins[pin].pin_desc, name,
324 1.6 jdc sizeof(sc->sc_pins[pin].pin_desc));
325 1.6 jdc /* envsys sensor # to pin # mapping */
326 1.6 jdc sc->sc_pins[envc].pin_sensor = pin;
327 1.6 jdc sc->sc_sensor[envc].state = ENVSYS_SINVALID;
328 1.6 jdc sc->sc_sensor[envc].units = ENVSYS_INDICATOR;
329 1.6 jdc strlcpy(sc->sc_sensor[envc].desc, name,
330 1.6 jdc sizeof(sc->sc_sensor[envc].desc));
331 1.6 jdc ret = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[envc]);
332 1.6 jdc if (ret) {
333 1.6 jdc sysmon_envsys_destroy(sc->sc_sme);
334 1.6 jdc sc->sc_sme = NULL;
335 1.6 jdc aprint_error_dev(sc->sc_dev,
336 1.6 jdc "unable to attach pin %d at sysmon\n", pin);
337 1.6 jdc return ret;
338 1.6 jdc }
339 1.6 jdc DPRINTF("%s: added sysmon: pin %d sensor %d (%s)\n",
340 1.6 jdc device_xname(sc->sc_dev), pin, envc, name);
341 1.6 jdc return 0;
342 1.6 jdc }
343 1.6 jdc
344 1.1 jdc void
345 1.1 jdc pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
346 1.1 jdc {
347 1.1 jdc struct pcf8574_softc *sc = sme->sme_cookie;
348 1.6 jdc int pin = sc->sc_pins[edata->sensor].pin_sensor;
349 1.6 jdc int act = sc->sc_pins[pin].pin_active;
350 1.6 jdc u_int8_t prev_state = sc->sc_state;
351 1.1 jdc
352 1.1 jdc pcf8574_read(sc, &sc->sc_state);
353 1.1 jdc if (act)
354 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE;
355 1.1 jdc else
356 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE;
357 1.1 jdc edata->state = ENVSYS_SVALID;
358 1.6 jdc
359 1.6 jdc /* We read all the pins, so check for alerts on any pin now */
360 1.6 jdc if (sc->sc_state != prev_state) {
361 1.6 jdc DPRINTF("%s: (refresh) status change: 0x%02x > 0x%02x\n",
362 1.6 jdc device_xname(sc->sc_dev), prev_state, sc->sc_state);
363 1.6 jdc pcf8574_check_alert(sc, prev_state, sc->sc_state);
364 1.6 jdc }
365 1.1 jdc }
366 1.1 jdc
367 1.1 jdc int
368 1.1 jdc pcf8574_get_led(void *cookie)
369 1.1 jdc {
370 1.1 jdc struct pcf8574_led *l = cookie;
371 1.1 jdc struct pcf8574_softc *sc = l->cookie;
372 1.1 jdc
373 1.1 jdc return ((sc->sc_state & l->mask) == l->v_on);
374 1.1 jdc }
375 1.1 jdc
376 1.1 jdc void
377 1.1 jdc pcf8574_set_led(void *cookie, int val)
378 1.1 jdc {
379 1.1 jdc struct pcf8574_led *l = cookie;
380 1.1 jdc struct pcf8574_softc *sc = l->cookie;
381 1.1 jdc uint32_t newstate;
382 1.1 jdc
383 1.1 jdc newstate = sc->sc_state & ~l->mask;
384 1.1 jdc newstate |= val ? l->v_on : l->v_off;
385 1.1 jdc DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n",
386 1.1 jdc device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off);
387 1.1 jdc if (newstate != sc->sc_state) {
388 1.1 jdc pcf8574_write(sc, newstate | sc->sc_mask);
389 1.1 jdc pcf8574_read(sc, &sc->sc_state);
390 1.1 jdc }
391 1.1 jdc }
392 1.1 jdc
393 1.1 jdc static void
394 1.1 jdc pcf8574_timeout(void *v)
395 1.1 jdc {
396 1.1 jdc struct pcf8574_softc *sc = v;
397 1.1 jdc
398 1.6 jdc sysmon_task_queue_sched(0, pcf8574_check, sc);
399 1.6 jdc callout_reset(&sc->sc_timer, hz * sc->sc_callout_time,
400 1.6 jdc pcf8574_timeout, sc);
401 1.6 jdc }
402 1.6 jdc
403 1.6 jdc static void
404 1.6 jdc pcf8574_check(void *v)
405 1.6 jdc {
406 1.6 jdc struct pcf8574_softc *sc = v;
407 1.6 jdc uint8_t prev_state = sc->sc_state;
408 1.6 jdc
409 1.6 jdc pcf8574_read(sc, &sc->sc_state);
410 1.6 jdc if (sc->sc_state != prev_state) {
411 1.6 jdc DPRINTF("%s: (check) status change: 0x%02x > 0x%02x\n",
412 1.6 jdc device_xname(sc->sc_dev), prev_state, sc->sc_state);
413 1.6 jdc pcf8574_check_alert(sc, prev_state, sc->sc_state);
414 1.6 jdc }
415 1.6 jdc }
416 1.6 jdc
417 1.1 jdc
418 1.6 jdc static void
419 1.6 jdc pcf8574_check_alert(struct pcf8574_softc *sc, uint8_t prev_state,
420 1.6 jdc uint8_t curr_state)
421 1.6 jdc {
422 1.6 jdc int i;
423 1.6 jdc uint8_t pin_chg;
424 1.6 jdc
425 1.6 jdc for (i = 0; i < PCF8574_NPINS; i++) {
426 1.6 jdc pin_chg = (sc->sc_state & 1 << i) ^ (prev_state & 1 << i);
427 1.6 jdc if (pin_chg & sc->sc_alert_mask) {
428 1.6 jdc if (sc->sc_pins[i].pin_active)
429 1.6 jdc printf("%s: Alert: %s = %s\n",
430 1.6 jdc device_xname(sc->sc_dev),
431 1.6 jdc sc->sc_pins[i].pin_desc,
432 1.6 jdc sc->sc_state & 1 << i ? "True" : "False");
433 1.6 jdc else
434 1.6 jdc printf("%s: Alert: %s = %s\n",
435 1.6 jdc device_xname(sc->sc_dev),
436 1.6 jdc sc->sc_pins[i].pin_desc,
437 1.6 jdc sc->sc_state & 1 << i ? "False" : "True");
438 1.6 jdc }
439 1.6 jdc }
440 1.1 jdc }
441