Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*	$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);
		}
	}
}