root/sys/arch/zaurus/dev/ioexp.c
/*      $NetBSD: ioexp.c,v 1.2 2018/06/16 21:22:13 thorpej Exp $        */

/*-
 * Copyright (c) 2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by NONAKA Kimihiro.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ioexp.c,v 1.2 2018/06/16 21:22:13 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/gpio.h>

#include <dev/i2c/i2cvar.h>

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

#include <zaurus/zaurus/zaurus_var.h>
#include <zaurus/dev/ioexpreg.h>
#include <zaurus/dev/ioexpvar.h>

#include "ioconf.h"

struct ioexp_softc {
        device_t        sc_dev;
        i2c_tag_t       sc_i2c;

        uint8_t         sc_output;
        uint8_t         sc_direction;

        int             sc_inited;
};

static int ioexp_match(device_t, cfdata_t, void *);
static void ioexp_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(ioexp, sizeof(struct ioexp_softc), 
    ioexp_match, ioexp_attach, NULL, NULL);

static uint8_t output_init_value = IOEXP_IR_ON | IOEXP_AKIN_PULLUP;
static uint8_t direction_init_value = 0;

static __inline int
ioexp_write(struct ioexp_softc *sc, uint8_t reg, uint8_t val)
{
        uint8_t cmd;
        uint8_t data;
        int error;

        cmd = reg;
        data = val;
        error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, IOEXP_ADDRESS,
            &cmd, 1, &data, 1, 0);
        return error;
}

static int
ioexp_match(device_t parent, cfdata_t cf, void *aux)
{
        struct i2c_attach_args *ia = aux;
        int match_result;

        /* only for SL-C1000 */
        if (!ZAURUS_ISC1000)
                return 0;

        if (iic_use_direct_match(ia, cf, NULL, &match_result))
                return match_result;
        
        /* indirect config - check typical address */
        if (ia->ia_addr == IOEXP_ADDRESS)
                return I2C_MATCH_ADDRESS_ONLY;

        return 0;
}

static void
ioexp_attach(device_t parent, device_t self, void *aux)
{
        struct ioexp_softc *sc = device_private(self);
        struct i2c_attach_args *ia = aux;

        sc->sc_dev = self;
        sc->sc_i2c = ia->ia_tag;

        aprint_normal(": GPIO controller\n");
        aprint_naive("\n");

        sc->sc_output = output_init_value;
        sc->sc_direction = direction_init_value;

        iic_acquire_bus(sc->sc_i2c, 0);
        ioexp_write(sc, IOEXP_POLARITY, 0);
        ioexp_write(sc, IOEXP_OUTPUT, sc->sc_output);
        ioexp_write(sc, IOEXP_DIRECTION, sc->sc_direction);
        iic_release_bus(sc->sc_i2c, 0);

        sc->sc_inited = 1;
}

#if 0
static void
ioexp_gpio_pin_ctl(struct ioexp_softc *sc, uint8_t bit, int flags,
    bool acquire_bus)
{
        int error;

        if (acquire_bus) {
                error = iic_acquire_bus(sc->sc_i2c, 0);
                if (error) {
                        aprint_error_dev(sc->sc_dev,
                            "unable to acquire bus. error=%d\n", error);
                        return;
                }
        }

        switch (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) {
        case GPIO_PIN_INPUT:
                sc->sc_direction |= bit;
                break;
        case GPIO_PIN_OUTPUT:
                sc->sc_direction &= ~bit;
                break;
        }
        error = ioexp_write(sc, IOEXP_DIRECTION, sc->sc_direction);
        if (error)
                aprint_error_dev(sc->sc_dev,
                    "direction write failed. error=%d\n", error);

        if (acquire_bus)
                iic_release_bus(sc->sc_i2c, 0);
}
#endif

static void
ioexp_gpio_pin_write(struct ioexp_softc *sc, uint8_t bit, int level,
    bool acquire_bus)
{
        int error;

        if (acquire_bus) {
                error = iic_acquire_bus(sc->sc_i2c, 0);
                if (error) {
                        aprint_error_dev(sc->sc_dev,
                            "unable to acquire bus. error=%d\n", error);
                        return;
                }
        }

        if (level == GPIO_PIN_LOW)
                sc->sc_output &= ~bit;
        else
                sc->sc_output |= bit;
        error = ioexp_write(sc, IOEXP_OUTPUT, sc->sc_output);
        if (error)
                aprint_error_dev(sc->sc_dev,
                    "output write failed. error=%d\n", error);

        if (acquire_bus)
                iic_release_bus(sc->sc_i2c, 0);
}

static __inline uint8_t
ioexp_gpio_pin_get(struct ioexp_softc *sc, uint8_t bit)
{

        return sc->sc_output & bit;
}

/*
 * Turn the LCD background light and contrast signal on or off.
 */
void
ioexp_set_backlight(int onoff, int cont)
{
        struct ioexp_softc *sc = device_lookup_private(&ioexp_cd, 0);

        if (sc == NULL || !sc->sc_inited) {
#ifdef DEBUG
                aprint_error("ioexp: %s: not attached or not inited\n",
                    __func__);
#endif
                if (onoff)
                        output_init_value |= IOEXP_BACKLIGHT_ON;
                else
                        output_init_value &= ~IOEXP_BACKLIGHT_ON;
                /* BACKLIGHT_CONT is inverted */
                if (cont)
                        output_init_value &= ~IOEXP_BACKLIGHT_CONT;
                else
                        output_init_value |= IOEXP_BACKLIGHT_CONT;
                return;
        }

        if (sc != NULL) {
                uint8_t bkreg = ioexp_gpio_pin_get(sc, IOEXP_BACKLIGHT_ON);
                uint8_t contreg = ioexp_gpio_pin_get(sc, IOEXP_BACKLIGHT_CONT);

                if (onoff && !bkreg) {
                        ioexp_gpio_pin_write(sc, IOEXP_BACKLIGHT_ON,
                            GPIO_PIN_HIGH, true);
                } else if (!onoff && bkreg) {
                        ioexp_gpio_pin_write(sc, IOEXP_BACKLIGHT_ON,
                            GPIO_PIN_LOW, true);
                }

                /* BACKLIGHT_CONT is inverted */
                if (cont && contreg) {
                        ioexp_gpio_pin_write(sc, IOEXP_BACKLIGHT_CONT,
                            GPIO_PIN_LOW, true);
                } else if (!cont && !contreg) {
                        ioexp_gpio_pin_write(sc, IOEXP_BACKLIGHT_CONT,
                            GPIO_PIN_HIGH, true);
                }
        }
}

/*
 * Turn the infrared LED on or off (must be on while transmitting).
 */
void
ioexp_set_irled(int onoff)
{
        struct ioexp_softc *sc = device_lookup_private(&ioexp_cd, 0);

        if (sc == NULL || !sc->sc_inited) {
#ifdef DEBUG
                aprint_error("ioexp: %s: not attached or not inited\n",
                    __func__);
#endif
                /* IR_ON is inverted */
                if (onoff)
                        output_init_value &= ~IOEXP_IR_ON;
                else
                        output_init_value |= IOEXP_IR_ON;
                return;
        }

        if (sc != NULL) {
                /* IR_ON is inverted */
                uint8_t reg = ioexp_gpio_pin_get(sc, IOEXP_IR_ON);
                if (onoff && reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_IR_ON, GPIO_PIN_LOW,
                            true);
                } else if (!onoff && !reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_IR_ON, GPIO_PIN_HIGH,
                            true);
                }
        }
}

/*
 * Enable or disable the mic bias
 */
void
ioexp_set_mic_bias(int onoff)
{
        struct ioexp_softc *sc = device_lookup_private(&ioexp_cd, 0);

        if (sc == NULL || !sc->sc_inited) {
#ifdef DEBUG
                aprint_error("ioexp: %s: not attached or not inited\n",
                    __func__);
#endif
                if (onoff)
                        output_init_value |= IOEXP_MIC_BIAS;
                else
                        output_init_value &= ~IOEXP_MIC_BIAS;
                return;
        }

        if (sc != NULL) {
                uint8_t reg = ioexp_gpio_pin_get(sc, IOEXP_MIC_BIAS);
                if (onoff && !reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_MIC_BIAS, GPIO_PIN_HIGH,
                            false);
                } else if (!onoff && reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_MIC_BIAS, GPIO_PIN_LOW,
                            false);
                }
        }
}

/*
 * Turn on pullup resistor while not reading the remote control.
 */
void
ioexp_akin_pullup(int onoff)
{
        struct ioexp_softc *sc = device_lookup_private(&ioexp_cd, 0);

        if (sc == NULL || !sc->sc_inited) {
#ifdef DEBUG
                aprint_error("ioexp: %s: not attached or not inited\n",
                    __func__);
#endif
                if (onoff)
                        output_init_value |= IOEXP_AKIN_PULLUP;
                else
                        output_init_value &= ~IOEXP_AKIN_PULLUP;
                return;
        }

        if (sc != NULL) {
                uint8_t reg = ioexp_gpio_pin_get(sc, IOEXP_AKIN_PULLUP);
                if (onoff && !reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_AKIN_PULLUP,
                            GPIO_PIN_HIGH, true);
                } else if (!onoff && reg) {
                        ioexp_gpio_pin_write(sc, IOEXP_AKIN_PULLUP,
                            GPIO_PIN_LOW, true);
                }
        }
}