1 /* $OpenBSD: aplmca.c,v 1.7 2023/07/26 11:09:24 kettenis Exp $ */ 2 /* 3 * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/param.h> 19 #include <sys/systm.h> 20 #include <sys/audioio.h> 21 #include <sys/device.h> 22 #include <sys/malloc.h> 23 #include <sys/fcntl.h> 24 25 #include <machine/bus.h> 26 #include <machine/fdt.h> 27 28 #include <dev/ofw/openfirm.h> 29 #include <dev/ofw/ofw_clock.h> 30 #include <dev/ofw/ofw_misc.h> 31 #include <dev/ofw/ofw_power.h> 32 #include <dev/ofw/fdt.h> 33 34 #include <dev/audio_if.h> 35 36 #include <arm64/dev/apldma.h> 37 38 /* 39 * This driver is based on preliminary device tree bindings and will 40 * almost certainly need changes once the official bindings land in 41 * mainline Linux. Support for these preliminary bindings will be 42 * dropped as soon as official bindings are available. 43 */ 44 45 #define MCA_CL_STRIDE 0x4000 46 #define MCA_SW_STRIDE 0x8000 47 #define MCA_SERDES_TXA 0x0300 48 49 #define MCA_STATUS(idx) ((idx) * MCA_CL_STRIDE + 0x0000) 50 #define MCA_STATUS_MCLK_EN (1 << 0) 51 #define MCA_MCLK_CONF(idx) ((idx) * MCA_CL_STRIDE + 0x0004) 52 #define MCA_MCLK_CONF_DIV_MASK (0xf << 8) 53 #define MCA_MCLK_CONF_DIV_SHIFT 8 54 55 #define MCA_SYNCGEN_STATUS(idx) ((idx) * MCA_CL_STRIDE + 0x0100) 56 #define MCA_SYNCGEN_STATUS_EN (1 << 0) 57 #define MCA_SYNCGEN_MCLK_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0104) 58 #define MCA_SYNCGEN_HI_PERIOD(idx) ((idx) * MCA_CL_STRIDE + 0x0108) 59 #define MCA_SYNCGEN_LO_PERIOD(idx) ((idx) * MCA_CL_STRIDE + 0x010c) 60 61 #define MCA_SERDES_BASE(idx, off) ((idx) * MCA_CL_STRIDE + (off)) 62 #define MCA_SERDES_STATUS(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0000) 63 #define MCA_SERDES_STATUS_EN (1 << 0) 64 #define MCA_SERDES_STATUS_RST (1 << 1) 65 #define MCA_SERDES_CONF(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0004) 66 #define MCA_SERDES_CONF_NSLOTS_MASK (0xf << 0) 67 #define MCA_SERDES_CONF_NSLOTS_SHIFT 0 68 #define MCA_SERDES_CONF_WIDTH_MASK (0x1f << 4) 69 #define MCA_SERDES_CONF_WIDTH_32BIT (0x10 << 4) 70 #define MCA_SERDES_CONF_BCLK_POL (1 << 10) 71 #define MCA_SERDES_CONF_MAGIC (0x7 << 12) 72 #define MCA_SERDES_CONF_SYNC_SEL_MASK (0x7 << 16) 73 #define MCA_SERDES_CONF_SYNC_SEL_SHIFT 16 74 #define MCA_SERDES_BITSTART(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0008) 75 #define MCA_SERDES_CHANMASK0(idx, off) (MCA_SERDES_BASE(idx, off) + 0x000c) 76 #define MCA_SERDES_CHANMASK1(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0010) 77 #define MCA_SERDES_CHANMASK2(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0014) 78 #define MCA_SERDES_CHANMASK3(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0018) 79 80 #define MCA_PORT_ENABLE(idx) ((idx) * MCA_CL_STRIDE + 0x0600) 81 #define MCA_PORT_ENABLE_CLOCKS (0x3 << 1) 82 #define MCA_PORT_ENABLE_TX_DATA (1 << 3) 83 #define MCA_PORT_CLOCK_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0604) 84 #define MCA_PORT_CLOCK_SEL_SHIFT 8 85 #define MCA_PORT_DATA_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0608) 86 #define MCA_PORT_DATA_SEL_TXA(idx) (1 << ((idx) * 2)) 87 #define MCA_PORT_DATA_SEL_TXB(idx) (2 << ((idx) * 2)) 88 89 #define MCA_DMA_ADAPTER_A(idx) ((idx) * MCA_SW_STRIDE + 0x0000) 90 #define MCA_DMA_ADAPTER_B(idx) ((idx) * MCA_SW_STRIDE + 0x4000) 91 #define MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT 0 92 #define MCA_DMA_ADAPTER_TX_NCHANS_SHIFT 5 93 #define MCA_DMA_ADAPTER_NCHANS_SHIFT 20 94 95 96 #define HREAD4(sc, reg) \ 97 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) 98 #define HWRITE4(sc, reg, val) \ 99 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 100 #define HSET4(sc, reg, bits) \ 101 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) 102 #define HCLR4(sc, reg, bits) \ 103 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) 104 105 struct aplmca_dai { 106 struct aplmca_softc *ad_sc; 107 struct dai_device ad_dai; 108 int ad_cluster; 109 110 struct apldma_channel *ad_ac; 111 void *ad_pbuf; 112 }; 113 114 struct aplmca_softc { 115 struct device sc_dev; 116 bus_space_tag_t sc_iot; 117 bus_space_handle_t sc_ioh; 118 bus_space_handle_t sc_sw_ioh; 119 120 int sc_node; 121 uint32_t sc_phandle; 122 123 int sc_nclusters; 124 struct aplmca_dai *sc_ad; 125 }; 126 127 int aplmca_set_format(void *, uint32_t, uint32_t, uint32_t); 128 int aplmca_set_sysclk(void *, uint32_t); 129 130 int aplmca_open(void *, int); 131 int aplmca_set_params(void *, int, int, 132 struct audio_params *, struct audio_params *); 133 void *aplmca_allocm(void *, int, size_t, int, int); 134 void aplmca_freem(void *, void *, int); 135 int aplmca_trigger_output(void *, void *, void *, int, 136 void (*)(void *), void *, struct audio_params *); 137 int aplmca_trigger_input(void *, void *, void *, int, 138 void (*)(void *), void *, struct audio_params *); 139 int aplmca_halt_output(void *); 140 int aplmca_halt_input(void *); 141 142 const struct audio_hw_if aplmca_hw_if = { 143 .open = aplmca_open, 144 .set_params = aplmca_set_params, 145 .allocm = aplmca_allocm, 146 .freem = aplmca_freem, 147 .trigger_output = aplmca_trigger_output, 148 .trigger_input = aplmca_trigger_input, 149 .halt_output = aplmca_halt_output, 150 .halt_input = aplmca_halt_input, 151 }; 152 153 int aplmca_match(struct device *, void *, void *); 154 void aplmca_attach(struct device *, struct device *, void *); 155 int aplmca_activate(struct device *, int); 156 157 const struct cfattach aplmca_ca = { 158 sizeof (struct aplmca_softc), aplmca_match, aplmca_attach, NULL, 159 aplmca_activate 160 }; 161 162 struct cfdriver aplmca_cd = { 163 NULL, "aplmca", DV_DULL 164 }; 165 166 int 167 aplmca_match(struct device *parent, void *match, void *aux) 168 { 169 struct fdt_attach_args *faa = aux; 170 171 return OF_is_compatible(faa->fa_node, "apple,mca"); 172 } 173 174 void 175 aplmca_attach(struct device *parent, struct device *self, void *aux) 176 { 177 struct aplmca_softc *sc = (struct aplmca_softc *)self; 178 struct fdt_attach_args *faa = aux; 179 int i; 180 181 if (faa->fa_nreg < 2) { 182 printf(": no registers\n"); 183 return; 184 } 185 186 sc->sc_iot = faa->fa_iot; 187 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 188 faa->fa_reg[0].size, 0, &sc->sc_ioh)) { 189 printf(": can't map registers\n"); 190 return; 191 } 192 if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr, 193 faa->fa_reg[1].size, 0, &sc->sc_sw_ioh)) { 194 bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size); 195 printf(": can't map registers\n"); 196 return; 197 } 198 199 sc->sc_node = faa->fa_node; 200 sc->sc_phandle = OF_getpropint(faa->fa_node, "phandle", 0); 201 202 sc->sc_nclusters = OF_getpropint(faa->fa_node, "apple,nclusters", 6); 203 sc->sc_ad = mallocarray(sc->sc_nclusters, sizeof(*sc->sc_ad), 204 M_DEVBUF, M_WAITOK | M_ZERO); 205 206 for (i = 0; i < sc->sc_nclusters; i++) { 207 sc->sc_ad[i].ad_cluster = i; 208 sc->sc_ad[i].ad_sc = sc; 209 sc->sc_ad[i].ad_dai.dd_node = sc->sc_node; 210 sc->sc_ad[i].ad_dai.dd_cookie = &sc->sc_ad[i]; 211 sc->sc_ad[i].ad_dai.dd_hw_if = &aplmca_hw_if; 212 sc->sc_ad[i].ad_dai.dd_set_format = aplmca_set_format; 213 sc->sc_ad[i].ad_dai.dd_set_sysclk = aplmca_set_sysclk; 214 } 215 216 printf("\n"); 217 218 power_domain_enable_idx(sc->sc_node, 0); 219 220 for (i = 0; i < sc->sc_nclusters; i++) { 221 HCLR4(sc, MCA_SERDES_STATUS(i, MCA_SERDES_TXA), 222 MCA_SERDES_STATUS_EN); 223 HCLR4(sc, MCA_SYNCGEN_STATUS(i), MCA_SYNCGEN_STATUS_EN); 224 HCLR4(sc, MCA_STATUS(i), MCA_STATUS_MCLK_EN); 225 } 226 } 227 228 int 229 aplmca_activate(struct device *self, int act) 230 { 231 struct aplmca_softc *sc = (struct aplmca_softc *)self; 232 int i; 233 234 switch (act) { 235 case DVACT_SUSPEND: 236 for (i = 0; i < sc->sc_nclusters; i++) { 237 if (sc->sc_ad[i].ad_ac) 238 power_domain_disable_idx(sc->sc_node, i + 1); 239 } 240 power_domain_disable_idx(sc->sc_node, 0); 241 break; 242 case DVACT_RESUME: 243 power_domain_enable_idx(sc->sc_node, 0); 244 for (i = 0; i < sc->sc_nclusters; i++) { 245 if (sc->sc_ad[i].ad_ac) 246 power_domain_enable_idx(sc->sc_node, i + 1); 247 } 248 break; 249 } 250 251 return 0; 252 } 253 254 int 255 aplmca_dai_init(struct aplmca_softc *sc, int port) 256 { 257 struct aplmca_dai *ad = &sc->sc_ad[port]; 258 uint32_t conf; 259 char name[5]; 260 int idx; 261 262 /* Allocate DMA channel. */ 263 snprintf(name, sizeof(name), "tx%da", ad->ad_cluster); 264 idx = OF_getindex(sc->sc_node, name, "dma-names"); 265 if (idx == -1) 266 return ENOENT; 267 ad->ad_ac = apldma_alloc_channel(idx); 268 if (ad->ad_ac == NULL) 269 return ENOENT; 270 271 power_domain_enable_idx(sc->sc_node, port + 1); 272 273 /* Basic SERDES configuration. */ 274 conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA)); 275 conf &= ~MCA_SERDES_CONF_SYNC_SEL_MASK; 276 conf |= (ad->ad_cluster + 1) << MCA_SERDES_CONF_SYNC_SEL_SHIFT; 277 conf |= MCA_SERDES_CONF_MAGIC; 278 HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf); 279 280 /* Output port configuration. */ 281 HWRITE4(sc, MCA_PORT_CLOCK_SEL(port), 282 (ad->ad_cluster + 1) << MCA_PORT_CLOCK_SEL_SHIFT); 283 HWRITE4(sc, MCA_PORT_DATA_SEL(port), 284 MCA_PORT_DATA_SEL_TXA(ad->ad_cluster)); 285 HWRITE4(sc, MCA_PORT_ENABLE(port), 286 MCA_PORT_ENABLE_CLOCKS | MCA_PORT_ENABLE_TX_DATA); 287 288 return 0; 289 } 290 291 void 292 aplmca_dai_link(struct aplmca_softc *sc, int master, int port) 293 { 294 struct aplmca_dai *ad = &sc->sc_ad[master]; 295 296 HWRITE4(sc, MCA_PORT_CLOCK_SEL(port), 297 (ad->ad_cluster + 1) << MCA_PORT_CLOCK_SEL_SHIFT); 298 HWRITE4(sc, MCA_PORT_DATA_SEL(port), 299 MCA_PORT_DATA_SEL_TXA(ad->ad_cluster)); 300 HWRITE4(sc, MCA_PORT_ENABLE(port), 301 MCA_PORT_ENABLE_CLOCKS | MCA_PORT_ENABLE_TX_DATA); 302 } 303 304 uint32_t * 305 aplmca_dai_next_dai(uint32_t *cells) 306 { 307 uint32_t phandle = cells[0]; 308 int node, ncells; 309 310 node = OF_getnodebyphandle(phandle); 311 if (node == 0) 312 return NULL; 313 314 ncells = OF_getpropint(node, "#sound-dai-cells", 0); 315 return cells + ncells + 1; 316 } 317 318 struct dai_device * 319 aplmca_alloc_cluster(int node) 320 { 321 struct aplmca_softc *sc = aplmca_cd.cd_devs[0]; 322 uint32_t *dais; 323 uint32_t *dai; 324 uint32_t ports[2]; 325 int nports = 0; 326 int len, i; 327 328 len = OF_getproplen(node, "sound-dai"); 329 if (len != 2 * sizeof(uint32_t) && len != 4 * sizeof(uint32_t)) 330 return NULL; 331 332 dais = malloc(len, M_TEMP, M_WAITOK); 333 OF_getpropintarray(node, "sound-dai", dais, len); 334 335 dai = dais; 336 while (dai && dai < dais + (len / sizeof(uint32_t))) { 337 if (dai[0] == sc->sc_phandle && nports < nitems(ports)) 338 ports[nports++] = dai[1]; 339 dai = aplmca_dai_next_dai(dai); 340 } 341 342 free(dais, M_TEMP, len); 343 344 if (nports == 0) 345 return NULL; 346 for (i = 0; i < nports; i++) { 347 if (ports[i] >= sc->sc_nclusters) 348 return NULL; 349 } 350 351 if (sc->sc_ad[ports[0]].ad_ac != NULL) 352 return NULL; 353 354 /* Setup the primary cluster. */ 355 if (aplmca_dai_init(sc, ports[0])) 356 return NULL; 357 358 /* 359 * Additional interfaces receive the same output as the 360 * primary interface by linking the output port to the primary 361 * cluster. 362 */ 363 for (i = 1; i < nports; i++) 364 aplmca_dai_link(sc, ports[0], ports[i]); 365 366 return &sc->sc_ad[ports[0]].ad_dai; 367 } 368 369 int 370 aplmca_set_format(void *cookie, uint32_t fmt, uint32_t pol, 371 uint32_t clk) 372 { 373 struct aplmca_dai *ad = cookie; 374 struct aplmca_softc *sc = ad->ad_sc; 375 uint32_t conf; 376 377 conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA)); 378 conf &= ~MCA_SERDES_CONF_WIDTH_MASK; 379 conf |= MCA_SERDES_CONF_WIDTH_32BIT; 380 381 switch (fmt) { 382 case DAI_FORMAT_I2S: 383 conf &= ~MCA_SERDES_CONF_BCLK_POL; 384 break; 385 case DAI_FORMAT_RJ: 386 case DAI_FORMAT_LJ: 387 conf |= MCA_SERDES_CONF_BCLK_POL; 388 break; 389 default: 390 return EINVAL; 391 } 392 393 if (pol & DAI_POLARITY_IB) 394 conf ^= MCA_SERDES_CONF_BCLK_POL; 395 if (pol & DAI_POLARITY_IF) 396 return EINVAL; 397 398 if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM)) 399 return EINVAL; 400 401 HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf); 402 403 return 0; 404 } 405 406 int 407 aplmca_set_sysclk(void *cookie, uint32_t rate) 408 { 409 struct aplmca_dai *ad = cookie; 410 struct aplmca_softc *sc = ad->ad_sc; 411 412 return clock_set_frequency_idx(sc->sc_node, ad->ad_cluster, rate); 413 } 414 415 int 416 aplmca_open(void *cookie, int flags) 417 { 418 if ((flags & (FWRITE | FREAD)) == (FWRITE | FREAD)) 419 return ENXIO; 420 421 return 0; 422 } 423 424 int 425 aplmca_set_params(void *cookie, int setmode, int usemode, 426 struct audio_params *play, struct audio_params *rec) 427 { 428 if (setmode & AUMODE_PLAY) { 429 play->sample_rate = 48000; 430 play->encoding = AUDIO_ENCODING_SLINEAR_LE; 431 play->precision = 24; 432 play->bps = 4; 433 play->msb = 0; 434 play->channels = 2; 435 } 436 437 return 0; 438 } 439 440 void * 441 aplmca_allocm(void *cookie, int direction, size_t size, int type, 442 int flags) 443 { 444 struct aplmca_dai *ad = cookie; 445 446 if (direction == AUMODE_PLAY) { 447 ad->ad_pbuf = apldma_allocm(ad->ad_ac, size, flags); 448 return ad->ad_pbuf; 449 } 450 451 return malloc(size, type, flags | M_ZERO); 452 } 453 454 void 455 aplmca_freem(void *cookie, void *addr, int type) 456 { 457 struct aplmca_dai *ad = cookie; 458 459 if (addr == ad->ad_pbuf) { 460 apldma_freem(ad->ad_ac); 461 return; 462 } 463 464 free(addr, type, 0); 465 } 466 467 int 468 aplmca_trigger_output(void *cookie, void *start, void *end, int blksize, 469 void (*intr)(void *), void *intrarg, struct audio_params *params) 470 { 471 struct aplmca_dai *ad = cookie; 472 struct aplmca_softc *sc = ad->ad_sc; 473 uint32_t conf, period; 474 int pad; 475 476 if (params->channels > 16) 477 return EINVAL; 478 479 /* Finalize SERDES configuration. */ 480 conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA)); 481 conf &= ~MCA_SERDES_CONF_NSLOTS_MASK; 482 conf |= ((params->channels - 1) << MCA_SERDES_CONF_NSLOTS_SHIFT); 483 HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf); 484 HWRITE4(sc, MCA_SERDES_CHANMASK0(ad->ad_cluster, MCA_SERDES_TXA), 485 0xffffffff); 486 HWRITE4(sc, MCA_SERDES_CHANMASK1(ad->ad_cluster, MCA_SERDES_TXA), 487 0xffffffff << params->channels); 488 HWRITE4(sc, MCA_SERDES_CHANMASK2(ad->ad_cluster, MCA_SERDES_TXA), 489 0xffffffff); 490 HWRITE4(sc, MCA_SERDES_CHANMASK3(ad->ad_cluster, MCA_SERDES_TXA), 491 0xffffffff << params->channels); 492 493 period = params->channels * 32; 494 HWRITE4(sc, MCA_SYNCGEN_HI_PERIOD(ad->ad_cluster), period - 2); 495 HWRITE4(sc, MCA_SYNCGEN_LO_PERIOD(ad->ad_cluster), 0); 496 HWRITE4(sc, MCA_MCLK_CONF(ad->ad_cluster), 497 1 << MCA_MCLK_CONF_DIV_SHIFT); 498 499 clock_enable_idx(sc->sc_node, ad->ad_cluster); 500 501 HWRITE4(sc, MCA_SYNCGEN_MCLK_SEL(ad->ad_cluster), 502 ad->ad_cluster + 1); 503 504 HSET4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN); 505 HSET4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster), 506 MCA_SYNCGEN_STATUS_EN); 507 HSET4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA), 508 MCA_SERDES_STATUS_EN); 509 510 pad = params->bps * 8 - params->precision; 511 bus_space_write_4(sc->sc_iot, sc->sc_sw_ioh, 512 MCA_DMA_ADAPTER_A(ad->ad_cluster), 513 pad << MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT | 514 2 << MCA_DMA_ADAPTER_TX_NCHANS_SHIFT | 515 2 << MCA_DMA_ADAPTER_NCHANS_SHIFT); 516 517 return apldma_trigger_output(ad->ad_ac, start, end, blksize, 518 intr, intrarg, params); 519 } 520 521 int 522 aplmca_trigger_input(void *cookie, void *start, void *end, int blksize, 523 void (*intr)(void *), void *intrarg, struct audio_params *params) 524 { 525 printf("%s\n", __func__); 526 return EIO; 527 } 528 529 int 530 aplmca_halt_output(void *cookie) 531 { 532 struct aplmca_dai *ad = cookie; 533 struct aplmca_softc *sc = ad->ad_sc; 534 int error; 535 536 error = apldma_halt_output(ad->ad_ac); 537 538 HCLR4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA), 539 MCA_SERDES_STATUS_EN); 540 HCLR4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster), 541 MCA_SYNCGEN_STATUS_EN); 542 HCLR4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN); 543 544 clock_disable_idx(sc->sc_node, ad->ad_cluster); 545 546 return error; 547 } 548 549 int 550 aplmca_halt_input(void *cookie) 551 { 552 printf("%s\n", __func__); 553 return 0; 554 } 555