/*      $NetBSD: wzero3_ssp.c,v 1.5 2012/01/21 19:44:29 nonaka Exp $    */

/*-
* Copyright (C) 2010 NONAKA Kimihiro <[email protected]>
* 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 ``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 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.
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: wzero3_ssp.c,v 1.5 2012/01/21 19:44:29 nonaka Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/mutex.h>
#include <sys/pmf.h>
#include <sys/bus.h>

#include <machine/bootinfo.h>
#include <machine/platid.h>
#include <machine/platid_mask.h>

#include <arm/xscale/pxa2x0reg.h>
#include <arm/xscale/pxa2x0var.h>
#include <arm/xscale/pxa2x0_gpio.h>

#include <hpcarm/dev/wzero3_reg.h>
#include <hpcarm/dev/wzero3_sspvar.h>

#define WS003SH_SSCR0_MAX1233   0x0000048f      /* 16bit/SPI/div by 5 */
#define WS007SH_SSCR0_ADS7846   0x000006ab      /* 12bit/Microwire/div by 7 */
#define WS011SH_SSCR0_AK4184_TP 0x0010068f      /* 32bit/SPI/div by 7 */
#define WS011SH_SSCR0_AK4184_KEYPAD 0x0000068f  /* 16bit/SPI/div by 7 */

struct wzero3ssp_model;
struct wzero3ssp_softc {
       device_t sc_dev;
       bus_space_tag_t sc_iot;
       bus_space_handle_t sc_ioh;
       kmutex_t sc_mtx;
       const struct wzero3ssp_model *sc_model;
};

static int      wzero3ssp_match(device_t, cfdata_t, void *);
static void     wzero3ssp_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(wzero3ssp, sizeof(struct wzero3ssp_softc),
       wzero3ssp_match, wzero3ssp_attach, NULL, NULL);

static void     wzero3ssp_init(struct wzero3ssp_softc *);
static bool     wzero3ssp_resume(device_t dv, const pmf_qual_t *);
static uint32_t wzero3ssp_read_ads7846(struct wzero3ssp_softc *, uint32_t);
static uint32_t wzero3ssp_read_max1233(struct wzero3ssp_softc *, uint32_t,
                   uint32_t);
static uint32_t wzero3ssp_read_ak4184_tp(struct wzero3ssp_softc *, uint32_t);
static uint16_t wzero3ssp_read_ak4184_keypad(struct wzero3ssp_softc *, uint32_t,
                   uint32_t);

static struct wzero3ssp_softc *wzero3ssp_sc;

static const struct wzero3ssp_model {
       platid_mask_t *platid;
       u_long sspaddr;
} wzero3ssp_table[] = {
       /* WS003SH */
       {
               &platid_mask_MACH_SHARP_WZERO3_WS003SH,
               PXA2X0_SSP2_BASE,
       },
       /* WS004SH */
       {
               &platid_mask_MACH_SHARP_WZERO3_WS004SH,
               PXA2X0_SSP2_BASE,
       },
       /* WS007SH */
       {
               &platid_mask_MACH_SHARP_WZERO3_WS007SH,
               PXA2X0_SSP1_BASE,
       },
       /* WS011SH */
       {
               &platid_mask_MACH_SHARP_WZERO3_WS011SH,
               PXA2X0_SSP1_BASE,
       },
#if 0
       /* WS0020H */
       {
               &platid_mask_MACH_SHARP_WZERO3_WS020SH,
               PXA2X0_SSP1_BASE,
       },
#endif
       {
               NULL, 0,
       },
};

static const struct wzero3ssp_model *
wzero3ssp_lookup(void)
{
       const struct wzero3ssp_model *model;

       for (model = wzero3ssp_table; model->platid != NULL; model++) {
               if (platid_match(&platid, model->platid)) {
                       return model;
               }
       }
       return NULL;
}

static int
wzero3ssp_match(device_t parent, cfdata_t cf, void *aux)
{

       if (strcmp(cf->cf_name, "wzero3ssp") != 0)
               return 0;
       if (wzero3ssp_lookup() == NULL)
               return 0;
       if (wzero3ssp_sc != NULL)
               return 0;
       return 1;
}

static void
wzero3ssp_attach(device_t parent, device_t self, void *aux)
{
       struct wzero3ssp_softc *sc = device_private(self);

       sc->sc_dev = self;
       wzero3ssp_sc = sc;

       aprint_normal("\n");
       aprint_naive("\n");

       sc->sc_model = wzero3ssp_lookup();
       if (sc->sc_model == NULL) {
               aprint_error_dev(self, "unknown model\n");
               return;
       }

       mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_TTY);

       sc->sc_iot = &pxa2x0_bs_tag;
       if (bus_space_map(sc->sc_iot, sc->sc_model->sspaddr, PXA2X0_SSP_SIZE, 0,
            &sc->sc_ioh)) {
               aprint_error_dev(sc->sc_dev, "can't map bus space\n");
               return;
       }

       if (!pmf_device_register(sc->sc_dev, NULL, wzero3ssp_resume))
               aprint_error_dev(sc->sc_dev,
                   "couldn't establish power handler\n");

       wzero3ssp_init(sc);
}

/*
* Initialize the dedicated SSP unit and disable all chip selects.
* This function is called with interrupts disabled.
*/
static void
wzero3ssp_init(struct wzero3ssp_softc *sc)
{

       if (sc->sc_model->sspaddr == PXA2X0_SSP1_BASE)
               pxa2x0_clkman_config(CKEN_SSP2, 1);
       else if (sc->sc_model->sspaddr == PXA2X0_SSP2_BASE)
               pxa2x0_clkman_config(CKEN_SSP3, 1);

       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR1, 0);

       /* XXX */
       if (platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS003SH)
        || platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS004SH)) {
               pxa2x0_gpio_set_function(39/*GPIO_WS003SH_XXX*/,
                   GPIO_OUT|GPIO_SET);
               pxa2x0_gpio_set_function(GPIO_WS003SH_MAX1233_CS,
                   GPIO_OUT|GPIO_SET);
       }
       if (platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS007SH)) {
               pxa2x0_gpio_set_function(GPIO_WS007SH_ADS7846_CS,
                   GPIO_OUT|GPIO_SET);
       }
       if (platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS011SH)) {
               pxa2x0_gpio_set_function(GPIO_WS011SH_AK4184_CS,
                   GPIO_OUT|GPIO_SET);
       }
}

static bool
wzero3ssp_resume(device_t dv, const pmf_qual_t *qual)
{
       struct wzero3ssp_softc *sc = device_private(dv);

       mutex_enter(&sc->sc_mtx);
       wzero3ssp_init(sc);
       mutex_exit(&sc->sc_mtx);

       return true;
}

/*
* Transmit a single data word to one of the ICs, keep the chip selected
* afterwards, and don't wait for data to be returned in SSDR.  Interrupts
* must be held off until wzero3ssp_ic_stop() gets called.
*/
void
wzero3ssp_ic_start(int ic, uint32_t cmd)
{
       struct wzero3ssp_softc *sc;

       KASSERT(wzero3ssp_sc != NULL);
       sc = wzero3ssp_sc;

       mutex_enter(&sc->sc_mtx);

       /* disable other ICs */
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       if (platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS007SH)) {
               if (ic != WZERO3_SSP_IC_ADS7846)
                       pxa2x0_gpio_set_bit(GPIO_WS007SH_ADS7846_CS);
       }
       if (platid_match(&platid, &platid_mask_MACH_SHARP_WZERO3_WS011SH)) {
               if (ic != WZERO3_SSP_IC_AK4184_TP
                && ic != WZERO3_SSP_IC_AK4184_KEYPAD)
                       pxa2x0_gpio_set_bit(GPIO_WS011SH_AK4184_CS);
       }

       /* activate the chosen one */
       switch (ic) {
       case WZERO3_SSP_IC_ADS7846:
               bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
               bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
                   WS007SH_SSCR0_ADS7846);
               pxa2x0_gpio_clear_bit(GPIO_WS007SH_ADS7846_CS);
               bus_space_write_1(sc->sc_iot, sc->sc_ioh, SSP_SSDR, cmd);
               while ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR)
                   & SSSR_TNF) != SSSR_TNF)
                       continue;       /* poll */
               break;
       case WZERO3_SSP_IC_AK4184_TP:
               bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
               bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
                   WS011SH_SSCR0_AK4184_TP);
               pxa2x0_gpio_clear_bit(GPIO_WS011SH_AK4184_CS);
               (void) bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);
               while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR)
                   & SSSR_TNF))
                       continue;       /* poll */
               bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR, cmd << 16);
               while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR)
                   & SSSR_BUSY)
                       continue;       /* poll */
               break;
       case WZERO3_SSP_IC_MAX1233:
       case WZERO3_SSP_IC_AK4184_KEYPAD:
       case WZERO3_SSP_IC_NUM:
       default:
               break;
       }
}

/*
* Read the last value from SSDR and deactivate all chip-selects.
*/
uint32_t
wzero3ssp_ic_stop(int ic)
{
       struct wzero3ssp_softc *sc;
       uint32_t rv;

       KASSERT(wzero3ssp_sc != NULL);
       sc = wzero3ssp_sc;

       switch (ic) {
       case WZERO3_SSP_IC_ADS7846:
               /* read result of last command */
               while ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR)
                   & SSSR_RNE) != SSSR_RNE)
                       continue;       /* poll */
               rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);
               pxa2x0_gpio_set_bit(GPIO_WS007SH_ADS7846_CS);
               break;
       case WZERO3_SSP_IC_AK4184_TP:
               /* read result of last command */
               while ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR)
                   & SSSR_RNE) != SSSR_RNE)
                       continue;       /* poll */
               rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);
               pxa2x0_gpio_set_bit(GPIO_WS011SH_AK4184_CS);
               break;
       case WZERO3_SSP_IC_MAX1233:
       case WZERO3_SSP_IC_AK4184_KEYPAD:
       case WZERO3_SSP_IC_NUM:
       default:
               rv = 0;
               break;
       }

       mutex_exit(&sc->sc_mtx);

       return rv;
}

/*
* Activate one of the chip-select lines, transmit one word value in
* each direction, and deactivate the chip-select again.
*/
uint32_t
wzero3ssp_ic_send(int ic, uint32_t data, uint32_t data2)
{
       struct wzero3ssp_softc *sc;

       if (wzero3ssp_sc == NULL) {
               aprint_error("%s: not configured\n", __func__);
               return 0;
       }
       sc = wzero3ssp_sc;

       switch (ic) {
       case WZERO3_SSP_IC_ADS7846:
               return wzero3ssp_read_ads7846(sc, data);
       case WZERO3_SSP_IC_MAX1233:
               return wzero3ssp_read_max1233(sc, data, data2);
       case WZERO3_SSP_IC_AK4184_TP:
               return wzero3ssp_read_ak4184_tp(sc, data);
       case WZERO3_SSP_IC_AK4184_KEYPAD:
               return wzero3ssp_read_ak4184_keypad(sc, data, data2);
       case WZERO3_SSP_IC_NUM:
       default:
               aprint_error("%s: invalid IC %d\n", __func__, ic);
               return 0;
       }
}

static uint32_t
wzero3ssp_read_ads7846(struct wzero3ssp_softc *sc, uint32_t cmd)
{
       uint32_t rv;

       mutex_enter(&sc->sc_mtx);

       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
           WS007SH_SSCR0_ADS7846);

       pxa2x0_gpio_clear_bit(GPIO_WS007SH_ADS7846_CS);

       /* send cmd */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR, cmd);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */

       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       pxa2x0_gpio_set_bit(GPIO_WS007SH_ADS7846_CS);

       mutex_exit(&sc->sc_mtx);

       return rv;
}

static uint32_t
wzero3ssp_read_max1233(struct wzero3ssp_softc *sc, uint32_t cmd, uint32_t data)
{
       uint32_t rv;

       mutex_enter(&sc->sc_mtx);

       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
           WS003SH_SSCR0_MAX1233);

       pxa2x0_gpio_set_bit(39/*GPIO_WS003SH_XXX*/);
       pxa2x0_gpio_clear_bit(GPIO_WS003SH_MAX1233_CS);

       /* send cmd */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR, cmd);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       (void)bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR, data);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       pxa2x0_gpio_set_bit(GPIO_WS003SH_MAX1233_CS);

       mutex_exit(&sc->sc_mtx);

       return rv;
}

static uint32_t
wzero3ssp_read_ak4184_tp(struct wzero3ssp_softc *sc, uint32_t cmd)
{
       uint32_t rv;

       mutex_enter(&sc->sc_mtx);

       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
           WS011SH_SSCR0_AK4184_TP);

       pxa2x0_gpio_clear_bit(GPIO_WS011SH_AK4184_CS);

       /* clear rx fifo */
       (void) bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       /* send cmd */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR, cmd << 16);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */

       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       rv = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       pxa2x0_gpio_set_bit(GPIO_WS011SH_AK4184_CS);

       mutex_exit(&sc->sc_mtx);

       return rv;
}

static uint16_t
wzero3ssp_read_ak4184_keypad(struct wzero3ssp_softc *sc, uint32_t cmd,
   uint32_t data)
{
       uint16_t rv;

       mutex_enter(&sc->sc_mtx);

       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0, 0);
       bus_space_write_4(sc->sc_iot, sc->sc_ioh, SSP_SSCR0,
           WS011SH_SSCR0_AK4184_KEYPAD);

       pxa2x0_gpio_clear_bit(GPIO_WS011SH_AK4184_CS);

       /* clear rx fifo */
       (void) bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       /* send cmd */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_2(sc->sc_iot, sc->sc_ioh, SSP_SSDR, (uint16_t)cmd);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       (void) bus_space_read_2(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_TNF))
               continue;       /* poll */
       bus_space_write_2(sc->sc_iot, sc->sc_ioh, SSP_SSDR, (uint16_t)data);
       while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_BUSY)
               continue;       /* poll */
       while (!(bus_space_read_4(sc->sc_iot, sc->sc_ioh, SSP_SSSR) & SSSR_RNE))
               continue;       /* poll */
       rv = bus_space_read_2(sc->sc_iot, sc->sc_ioh, SSP_SSDR);

       pxa2x0_gpio_set_bit(GPIO_WS011SH_AK4184_CS);

       mutex_exit(&sc->sc_mtx);

       return rv;
}