root/sys/arch/hpcarm/dev/wzero3_ssp.c
/*      $NetBSD: wzero3_ssp.c,v 1.5 2012/01/21 19:44:29 nonaka Exp $    */

/*-
 * Copyright (C) 2010 NONAKA Kimihiro <nonaka@netbsd.org>
 * 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;
}