1 1.10 christos /* $NetBSD: pcf8574.c,v 1.10 2021/06/21 03:12:54 christos 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.10 christos __KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.10 2021/06/21 03:12:54 christos 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.9 thorpej DEVICE_COMPAT_EOL 113 1.1 jdc }; 114 1.1 jdc 115 1.1 jdc static int 116 1.1 jdc pcf8574_match(device_t parent, cfdata_t cf, void *aux) 117 1.1 jdc { 118 1.1 jdc struct i2c_attach_args *ia = aux; 119 1.1 jdc int match_result; 120 1.1 jdc 121 1.5 jdc if (iic_use_direct_match(ia, cf, compat_data, &match_result)) 122 1.5 jdc return match_result; 123 1.1 jdc 124 1.1 jdc /* We don't support indirect matches */ 125 1.1 jdc return 0; 126 1.1 jdc } 127 1.1 jdc 128 1.1 jdc static void 129 1.1 jdc pcf8574_attach(device_t parent, device_t self, void *aux) 130 1.1 jdc { 131 1.1 jdc struct pcf8574_softc *sc = device_private(self); 132 1.1 jdc struct i2c_attach_args *ia = aux; 133 1.1 jdc prop_dictionary_t dict = device_properties(self); 134 1.1 jdc prop_array_t pins; 135 1.1 jdc prop_dictionary_t pin; 136 1.1 jdc int i, num, def, envc = 0; 137 1.1 jdc char name[32]; 138 1.1 jdc const char *nptr = NULL, *spptr; 139 1.6 jdc bool ok = TRUE, act; 140 1.1 jdc 141 1.1 jdc sc->sc_tag = ia->ia_tag; 142 1.1 jdc sc->sc_addr = ia->ia_addr; 143 1.1 jdc sc->sc_dev = self; 144 1.1 jdc 145 1.6 jdc sc->sc_sme = NULL; 146 1.6 jdc #ifdef PCF8574_DEBUG 147 1.6 jdc sc->sc_callout_time = 60; /* watch for changes when debugging */ 148 1.6 jdc #else 149 1.6 jdc sc->sc_callout_time = 0; 150 1.6 jdc #endif 151 1.6 jdc 152 1.1 jdc /* 153 1.1 jdc * The PCF8574 requires input pins to be written with the value 1, 154 1.1 jdc * and then read. Assume that all pins are input initially. 155 1.1 jdc * We'll use the mask when we write, because we have to write a 156 1.1 jdc * value for every pin, and we don't want to change input pins. 157 1.1 jdc */ 158 1.1 jdc sc->sc_mask = 0xff; 159 1.1 jdc 160 1.5 jdc /* Try a read, and fail if this component isn't present */ 161 1.5 jdc if (pcf8574_read(sc, &sc->sc_state)) { 162 1.5 jdc aprint_normal(": read failed\n"); 163 1.5 jdc return; 164 1.5 jdc } 165 1.1 jdc 166 1.1 jdc #ifdef PCF8574_DEBUG 167 1.1 jdc aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state); 168 1.1 jdc #else 169 1.1 jdc aprint_normal(": GPIO\n"); 170 1.1 jdc #endif 171 1.1 jdc 172 1.1 jdc pins = prop_dictionary_get(dict, "pins"); 173 1.1 jdc if (pins == NULL) 174 1.1 jdc return; 175 1.1 jdc 176 1.1 jdc for (i = 0; i < prop_array_count(pins); i++) { 177 1.1 jdc pin = prop_array_get(pins, i); 178 1.10 christos ok &= prop_dictionary_get_string(pin, "name", &nptr); 179 1.1 jdc ok &= prop_dictionary_get_uint32(pin, "pin", &num); 180 1.1 jdc ok &= prop_dictionary_get_bool(pin, "active_high", &act); 181 1.1 jdc /* optional default state */ 182 1.1 jdc def = -1; 183 1.1 jdc prop_dictionary_get_int32(pin, "default_state", &def); 184 1.1 jdc if (!ok) 185 1.1 jdc continue; 186 1.1 jdc /* Extract pin type from the name */ 187 1.1 jdc spptr = strstr(nptr, " "); 188 1.1 jdc if (spptr == NULL) 189 1.1 jdc continue; 190 1.1 jdc spptr += 1; 191 1.1 jdc strncpy(name, spptr, 31); 192 1.6 jdc sc->sc_pins[i].pin_active = act; 193 1.1 jdc if (!strncmp(nptr, "LED ", 4)) { 194 1.1 jdc sc->sc_mask &= ~(1 << num); 195 1.1 jdc pcf8574_attach_led(sc, name, num, act, def); 196 1.1 jdc } 197 1.6 jdc if (!strncmp(nptr, "INDICATOR ", 10)) { 198 1.6 jdc if (pcf8574_attach_sysmon(sc, name, envc, num, act)) 199 1.6 jdc return; 200 1.6 jdc envc++; 201 1.6 jdc } 202 1.6 jdc if (!strncmp(nptr, "ALERT ", 6)) { 203 1.6 jdc u_int64_t alert_time; 204 1.6 jdc 205 1.6 jdc if (pcf8574_attach_sysmon(sc, name, envc, num, act)) 206 1.1 jdc return; 207 1.6 jdc 208 1.6 jdc /* Adjust our timeout if the alert times out. */ 209 1.6 jdc if (def != -1) 210 1.6 jdc alert_time = def / 2; 211 1.6 jdc else 212 1.6 jdc alert_time = PCF8574_DEFAULT_TIMER; 213 1.6 jdc 214 1.6 jdc if (sc->sc_callout_time == 0 || 215 1.6 jdc alert_time < sc->sc_callout_time) 216 1.6 jdc sc->sc_callout_time = alert_time; 217 1.6 jdc 218 1.6 jdc sc->sc_alert_mask |= (1 << num); 219 1.1 jdc envc++; 220 1.1 jdc } 221 1.1 jdc } 222 1.1 jdc 223 1.6 jdc if (sc->sc_sme != NULL) { 224 1.1 jdc sc->sc_sme->sme_name = device_xname(self); 225 1.1 jdc sc->sc_sme->sme_cookie = sc; 226 1.1 jdc sc->sc_sme->sme_refresh = pcf8574_refresh; 227 1.1 jdc if (sysmon_envsys_register(sc->sc_sme)) { 228 1.1 jdc aprint_error_dev(self, 229 1.1 jdc "unable to register with sysmon\n"); 230 1.1 jdc sysmon_envsys_destroy(sc->sc_sme); 231 1.3 jdc sc->sc_sme = NULL; 232 1.1 jdc return; 233 1.1 jdc } 234 1.1 jdc } 235 1.6 jdc 236 1.6 jdc if (sc->sc_callout_time) { 237 1.6 jdc callout_init(&sc->sc_timer, CALLOUT_MPSAFE); 238 1.6 jdc callout_reset(&sc->sc_timer, hz * sc->sc_callout_time, 239 1.6 jdc pcf8574_timeout, sc); 240 1.6 jdc } 241 1.1 jdc } 242 1.1 jdc 243 1.1 jdc static int 244 1.1 jdc pcf8574_detach(device_t self, int flags) 245 1.1 jdc { 246 1.1 jdc struct pcf8574_softc *sc = device_private(self); 247 1.2 jdc int i; 248 1.2 jdc 249 1.6 jdc if (sc->sc_callout_time) { 250 1.6 jdc callout_halt(&sc->sc_timer, NULL); 251 1.6 jdc callout_destroy(&sc->sc_timer); 252 1.6 jdc sc->sc_callout_time = 0; 253 1.6 jdc } 254 1.6 jdc 255 1.3 jdc if (sc->sc_sme != NULL) { 256 1.2 jdc sysmon_envsys_unregister(sc->sc_sme); 257 1.3 jdc sc->sc_sme = NULL; 258 1.3 jdc } 259 1.2 jdc 260 1.2 jdc for (i = 0; i < sc->sc_nleds; i++) 261 1.2 jdc led_detach(sc->sc_leds[i].led); 262 1.1 jdc 263 1.1 jdc return 0; 264 1.1 jdc } 265 1.1 jdc 266 1.1 jdc static int 267 1.1 jdc pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) 268 1.1 jdc { 269 1.1 jdc int err = 0; 270 1.1 jdc 271 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 272 1.1 jdc return err; 273 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, 274 1.1 jdc sc->sc_addr, NULL, 0, val, 1, 0); 275 1.1 jdc iic_release_bus(sc->sc_tag, 0); 276 1.1 jdc return err; 277 1.1 jdc } 278 1.1 jdc 279 1.1 jdc static int 280 1.1 jdc pcf8574_write(struct pcf8574_softc *sc, uint8_t val) 281 1.1 jdc { 282 1.1 jdc int err = 0; 283 1.1 jdc 284 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 285 1.1 jdc return err; 286 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 287 1.1 jdc &val, 1, NULL, 0, 0); 288 1.1 jdc iic_release_bus(sc->sc_tag, 0); 289 1.1 jdc return err; 290 1.1 jdc } 291 1.1 jdc 292 1.1 jdc static void 293 1.6 jdc pcf8574_attach_led(struct pcf8574_softc *sc, char *name, int pin, int act, 294 1.6 jdc int def) 295 1.1 jdc { 296 1.1 jdc struct pcf8574_led *l; 297 1.1 jdc 298 1.1 jdc l = &sc->sc_leds[sc->sc_nleds]; 299 1.1 jdc l->cookie = sc; 300 1.1 jdc l->mask = 1 << pin; 301 1.1 jdc l->v_on = act ? l->mask : 0; 302 1.1 jdc l->v_off = act ? 0 : l->mask; 303 1.6 jdc led_attach(name, l, pcf8574_get_led, pcf8574_set_led); 304 1.1 jdc if (def != -1) 305 1.1 jdc pcf8574_set_led(l, def); 306 1.1 jdc DPRINTF("%s: attached LED: %02x %02x %02x def %d\n", 307 1.1 jdc device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def); 308 1.1 jdc sc->sc_nleds++; 309 1.1 jdc } 310 1.1 jdc 311 1.6 jdc static int 312 1.6 jdc pcf8574_attach_sysmon(struct pcf8574_softc *sc, char *name, int envc, int pin, 313 1.6 jdc int act) 314 1.6 jdc { 315 1.6 jdc int ret; 316 1.6 jdc 317 1.6 jdc if (sc->sc_sme == NULL) { 318 1.6 jdc sc->sc_sme = sysmon_envsys_create(); 319 1.6 jdc sc->sc_sme->sme_events_timeout = 0; 320 1.6 jdc } 321 1.6 jdc 322 1.6 jdc strlcpy(sc->sc_pins[pin].pin_desc, name, 323 1.6 jdc sizeof(sc->sc_pins[pin].pin_desc)); 324 1.6 jdc /* envsys sensor # to pin # mapping */ 325 1.6 jdc sc->sc_pins[envc].pin_sensor = pin; 326 1.6 jdc sc->sc_sensor[envc].state = ENVSYS_SINVALID; 327 1.6 jdc sc->sc_sensor[envc].units = ENVSYS_INDICATOR; 328 1.6 jdc strlcpy(sc->sc_sensor[envc].desc, name, 329 1.6 jdc sizeof(sc->sc_sensor[envc].desc)); 330 1.6 jdc ret = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[envc]); 331 1.6 jdc if (ret) { 332 1.6 jdc sysmon_envsys_destroy(sc->sc_sme); 333 1.6 jdc sc->sc_sme = NULL; 334 1.6 jdc aprint_error_dev(sc->sc_dev, 335 1.6 jdc "unable to attach pin %d at sysmon\n", pin); 336 1.6 jdc return ret; 337 1.6 jdc } 338 1.6 jdc DPRINTF("%s: added sysmon: pin %d sensor %d (%s)\n", 339 1.6 jdc device_xname(sc->sc_dev), pin, envc, name); 340 1.6 jdc return 0; 341 1.6 jdc } 342 1.6 jdc 343 1.1 jdc void 344 1.1 jdc pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 345 1.1 jdc { 346 1.1 jdc struct pcf8574_softc *sc = sme->sme_cookie; 347 1.6 jdc int pin = sc->sc_pins[edata->sensor].pin_sensor; 348 1.6 jdc int act = sc->sc_pins[pin].pin_active; 349 1.6 jdc u_int8_t prev_state = sc->sc_state; 350 1.1 jdc 351 1.1 jdc pcf8574_read(sc, &sc->sc_state); 352 1.1 jdc if (act) 353 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE; 354 1.1 jdc else 355 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE; 356 1.1 jdc edata->state = ENVSYS_SVALID; 357 1.6 jdc 358 1.6 jdc /* We read all the pins, so check for alerts on any pin now */ 359 1.6 jdc if (sc->sc_state != prev_state) { 360 1.6 jdc DPRINTF("%s: (refresh) status change: 0x%02x > 0x%02x\n", 361 1.6 jdc device_xname(sc->sc_dev), prev_state, sc->sc_state); 362 1.6 jdc pcf8574_check_alert(sc, prev_state, sc->sc_state); 363 1.6 jdc } 364 1.1 jdc } 365 1.1 jdc 366 1.1 jdc int 367 1.1 jdc pcf8574_get_led(void *cookie) 368 1.1 jdc { 369 1.1 jdc struct pcf8574_led *l = cookie; 370 1.1 jdc struct pcf8574_softc *sc = l->cookie; 371 1.1 jdc 372 1.1 jdc return ((sc->sc_state & l->mask) == l->v_on); 373 1.1 jdc } 374 1.1 jdc 375 1.1 jdc void 376 1.1 jdc pcf8574_set_led(void *cookie, int val) 377 1.1 jdc { 378 1.1 jdc struct pcf8574_led *l = cookie; 379 1.1 jdc struct pcf8574_softc *sc = l->cookie; 380 1.1 jdc uint32_t newstate; 381 1.1 jdc 382 1.1 jdc newstate = sc->sc_state & ~l->mask; 383 1.1 jdc newstate |= val ? l->v_on : l->v_off; 384 1.1 jdc DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n", 385 1.1 jdc device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 386 1.1 jdc if (newstate != sc->sc_state) { 387 1.1 jdc pcf8574_write(sc, newstate | sc->sc_mask); 388 1.1 jdc pcf8574_read(sc, &sc->sc_state); 389 1.1 jdc } 390 1.1 jdc } 391 1.1 jdc 392 1.1 jdc static void 393 1.1 jdc pcf8574_timeout(void *v) 394 1.1 jdc { 395 1.1 jdc struct pcf8574_softc *sc = v; 396 1.1 jdc 397 1.6 jdc sysmon_task_queue_sched(0, pcf8574_check, sc); 398 1.6 jdc callout_reset(&sc->sc_timer, hz * sc->sc_callout_time, 399 1.6 jdc pcf8574_timeout, sc); 400 1.6 jdc } 401 1.6 jdc 402 1.6 jdc static void 403 1.6 jdc pcf8574_check(void *v) 404 1.6 jdc { 405 1.6 jdc struct pcf8574_softc *sc = v; 406 1.6 jdc uint8_t prev_state = sc->sc_state; 407 1.6 jdc 408 1.6 jdc pcf8574_read(sc, &sc->sc_state); 409 1.6 jdc if (sc->sc_state != prev_state) { 410 1.6 jdc DPRINTF("%s: (check) status change: 0x%02x > 0x%02x\n", 411 1.6 jdc device_xname(sc->sc_dev), prev_state, sc->sc_state); 412 1.6 jdc pcf8574_check_alert(sc, prev_state, sc->sc_state); 413 1.6 jdc } 414 1.6 jdc } 415 1.6 jdc 416 1.1 jdc 417 1.6 jdc static void 418 1.6 jdc pcf8574_check_alert(struct pcf8574_softc *sc, uint8_t prev_state, 419 1.6 jdc uint8_t curr_state) 420 1.6 jdc { 421 1.6 jdc int i; 422 1.6 jdc uint8_t pin_chg; 423 1.6 jdc 424 1.6 jdc for (i = 0; i < PCF8574_NPINS; i++) { 425 1.6 jdc pin_chg = (sc->sc_state & 1 << i) ^ (prev_state & 1 << i); 426 1.6 jdc if (pin_chg & sc->sc_alert_mask) { 427 1.6 jdc if (sc->sc_pins[i].pin_active) 428 1.6 jdc printf("%s: Alert: %s = %s\n", 429 1.6 jdc device_xname(sc->sc_dev), 430 1.6 jdc sc->sc_pins[i].pin_desc, 431 1.6 jdc sc->sc_state & 1 << i ? "True" : "False"); 432 1.6 jdc else 433 1.6 jdc printf("%s: Alert: %s = %s\n", 434 1.6 jdc device_xname(sc->sc_dev), 435 1.6 jdc sc->sc_pins[i].pin_desc, 436 1.6 jdc sc->sc_state & 1 << i ? "False" : "True"); 437 1.6 jdc } 438 1.6 jdc } 439 1.1 jdc } 440