/* $NetBSD: ppbus_conf.c,v 1.23 2021/08/07 16:19:15 thorpej Exp $ */

/*-
* Copyright (c) 1997, 1998, 1999 Nicolas Souchu
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* FreeBSD: src/sys/dev/ppbus/ppbconf.c,v 1.17.2.1 2000/05/24 00:20:57 n_hibma Exp
*
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ppbus_conf.c,v 1.23 2021/08/07 16:19:15 thorpej Exp $");

#include "opt_ppbus.h"
#include "opt_ppbus_1284.h"

#include "gpio.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/proc.h>

#include <dev/ppbus/ppbus_1284.h>
#include <dev/ppbus/ppbus_base.h>
#include <dev/ppbus/ppbus_conf.h>
#include <dev/ppbus/ppbus_device.h>
#include <dev/ppbus/ppbus_var.h>

/* Probe, attach, and detach functions for ppbus. */
static int ppbus_probe(device_t, cfdata_t, void *);
static void ppbus_attach(device_t, device_t, void *);
static void ppbus_childdet(device_t, device_t);
static int ppbus_detach(device_t, int);

/* Utility function prototypes */
static int ppbus_search_children(device_t, cfdata_t,
                                const int *, void *);


CFATTACH_DECL2_NEW(ppbus, sizeof(struct ppbus_softc), ppbus_probe, ppbus_attach,
       ppbus_detach, NULL, NULL, ppbus_childdet);

/* Probe function for ppbus. */
static int
ppbus_probe(device_t parent, cfdata_t cf, void *aux)
{
       struct parport_adapter *sc_link = aux;

       /* Check adapter for consistency */
       if (
               /* Required methods for all parports */
               sc_link->parport_io == NULL ||
               sc_link->parport_exec_microseq == NULL ||
               sc_link->parport_setmode == NULL ||
               sc_link->parport_getmode == NULL ||
               sc_link->parport_read == NULL ||
               sc_link->parport_write == NULL ||
               sc_link->parport_read_ivar == NULL ||
               sc_link->parport_write_ivar == NULL ||
               /* Methods which conditional exist based on capabilities */
               ((sc_link->capabilities & PPBUS_HAS_EPP) &&
               (sc_link->parport_reset_epp_timeout == NULL)) ||
               ((sc_link->capabilities & PPBUS_HAS_ECP) &&
               (sc_link->parport_ecp_sync == NULL)) ||
               ((sc_link->capabilities & PPBUS_HAS_DMA) &&
               (sc_link->parport_dma_malloc == NULL ||
               sc_link->parport_dma_free == NULL)) ||
               ((sc_link->capabilities & PPBUS_HAS_INTR) &&
               (sc_link->parport_add_handler == NULL ||
               sc_link->parport_remove_handler == NULL))
               ) {

#ifdef PPBUS_DEBUG
               printf("%s(%s): parport_adaptor is incomplete. Child device "
                       "probe failed.\n", __func__, device_xname(parent));
#endif
               return 0;
       } else {
               return 1;
       }
}

/* Attach function for ppbus. */
static void
ppbus_attach(device_t parent, device_t self, void *aux)
{
       struct ppbus_softc *ppbus = device_private(self);
       struct parport_adapter *sc_link = aux;
       struct ppbus_attach_args args;

       printf("\n");

       /* Initialize config data from adapter (bus + device methods) */
       ppbus->sc_dev = self;
       args.capabilities = ppbus->sc_capabilities = sc_link->capabilities;
       ppbus->ppbus_io = sc_link->parport_io;
       ppbus->ppbus_exec_microseq = sc_link->parport_exec_microseq;
       ppbus->ppbus_reset_epp_timeout = sc_link->
               parport_reset_epp_timeout;
       ppbus->ppbus_setmode = sc_link->parport_setmode;
       ppbus->ppbus_getmode = sc_link->parport_getmode;
       ppbus->ppbus_ecp_sync = sc_link->parport_ecp_sync;
       ppbus->ppbus_read = sc_link->parport_read;
       ppbus->ppbus_write = sc_link->parport_write;
       ppbus->ppbus_read_ivar = sc_link->parport_read_ivar;
       ppbus->ppbus_write_ivar = sc_link->parport_write_ivar;
       ppbus->ppbus_dma_malloc = sc_link->parport_dma_malloc;
       ppbus->ppbus_dma_free = sc_link->parport_dma_free;
       ppbus->ppbus_add_handler = sc_link->parport_add_handler;
       ppbus->ppbus_remove_handler = sc_link->parport_remove_handler;

       /* Initially there is no device owning the bus */
       ppbus->ppbus_owner = NULL;

       /* Initialize locking structures */
       mutex_init(&(ppbus->sc_lock), MUTEX_DEFAULT, IPL_NONE);

       /* Set up bus mode and ieee state */
       ppbus->sc_mode = ppbus->ppbus_getmode(device_parent(self));
       ppbus->sc_use_ieee = 1;
       ppbus->sc_1284_state = PPBUS_FORWARD_IDLE;
       ppbus->sc_1284_error = PPBUS_NO_ERROR;

       /* Record device's successful attachment */
       ppbus->sc_dev_ok = PPBUS_OK;

#ifndef DONTPROBE_1284
       /* detect IEEE1284 compliant devices */
       if (ppbus_scan_bus(self)) {
               printf("%s: No IEEE1284 device found.\n", device_xname(self));
       } else {
               printf("%s: IEEE1284 device found.\n", device_xname(self));
               /*
                * Detect device ID (interrupts must be disabled because we
                * cannot do a block to wait for it - no context)
                */
               if (args.capabilities & PPBUS_HAS_INTR) {
                       int val = 0;
                       if(ppbus_write_ivar(self, PPBUS_IVAR_INTR, &val) != 0) {
                               printf(" <problem initializing interrupt "
                                       "usage>");
                       }
               }
               ppbus_pnp_detect(self);
       }
#endif /* !DONTPROBE_1284 */

       /* Configure child devices */
       SLIST_INIT(&(ppbus->sc_childlist_head));
       config_search(self, &args,
           CFARGS(.search = ppbus_search_children,
                  .iattr = "ppbus"));

#if NGPIO > 0
       gpio_ppbus_attach(ppbus);
#endif
       return;
}

static void
ppbus_childdet(device_t self, device_t target)
{
       struct ppbus_softc * ppbus = device_private(self);
       struct ppbus_device_softc * child;

       SLIST_FOREACH(child, &ppbus->sc_childlist_head, entries) {
               if (child->sc_dev == target)
                       break;
       }
       if (child != NULL)
               SLIST_REMOVE(&ppbus->sc_childlist_head, child,
                   ppbus_device_softc, entries);
}

/* Detach function for ppbus. */
static int
ppbus_detach(device_t self, int flag)
{
       struct ppbus_softc * ppbus = device_private(self);
       struct ppbus_device_softc * child;

       if (ppbus->sc_dev_ok != PPBUS_OK) {
               if (!(flag & DETACH_QUIET))
                       printf("%s: detach called on unattached device.\n",
                               device_xname(ppbus->sc_dev));
               if (!(flag & DETACH_FORCE))
                       return 0;
               if (!(flag & DETACH_QUIET))
                       printf("%s: continuing detach (DETACH_FORCE).\n",
                               device_xname(ppbus->sc_dev));
       }

       mutex_destroy(&(ppbus->sc_lock));

       /* Detach children devices */
       while ((child = SLIST_FIRST(&ppbus->sc_childlist_head)) != NULL) {
               if (config_detach(child->sc_dev, flag)) {
                       if(!(flag & DETACH_QUIET))
                               aprint_error_dev(ppbus->sc_dev, "error detaching %s.",
                                       device_xname(child->sc_dev));
                       if(!(flag & DETACH_FORCE))
                               return 0;
                       if(!(flag & DETACH_QUIET))
                               printf("%s: continuing (DETACH_FORCE).\n",
                                       device_xname(ppbus->sc_dev));
               }
       }

       if (!(flag & DETACH_QUIET))
               printf("%s: detached.\n", device_xname(ppbus->sc_dev));

       return 1;
}

/* Search for children device and add to list */
static int
ppbus_search_children(device_t parent, cfdata_t cf, const int *ldesc, void *aux)
{
       struct ppbus_softc *ppbus = device_private(parent);
       struct ppbus_device_softc *child;
       device_t dev;
       int rval = 0;

       if (config_probe(parent, cf, aux)) {
               dev = config_attach(parent, cf, aux, NULL, CFARGS_NONE);
               if (dev) {
                       child = device_private(dev);
                       SLIST_INSERT_HEAD(&(ppbus->sc_childlist_head),
                               child, entries);
                       rval = 1;
               }
       }

       return rval;
}