NetBSD/sys/dev/pci/voyager.c
drochner d8e1a7b61a Use pci_aprint_devinfo(9) instead of pci_devinfo+aprint_{normal,naive}
where it looks straightforward, and pci_aprint_devinfo_fancy in a few
others where drivers want to supply their own device names instead
of the pcidevs generated one. More complicated cases, where names
are composed at runtime, are left alone for now. It certainly makes
sense to simplify the drivers here rather than inventing a catch-all API.
This should serve as as example for new drivers, and also ensure
consistent output in the AB_QUIET ("boot -q") case. Also, it avoids
excessive stack usage where drivers attach child devices because the
buffer for the device name is not kept on the local stack anymore.
2012-01-30 19:41:18 +00:00

495 lines
13 KiB
C

/* $NetBSD: voyager.c,v 1.9 2012/01/30 19:41:23 drochner Exp $ */
/*
* Copyright (c) 2009, 2011 Michael Lorenz
* 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: voyager.c,v 1.9 2012/01/30 19:41:23 drochner Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/lwp.h>
#include <sys/kauth.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pciio.h>
#include <dev/ic/sm502reg.h>
#include <dev/i2c/i2cvar.h>
#include <dev/i2c/i2c_bitbang.h>
#include <sys/evcnt.h>
#include <sys/bitops.h>
#include <dev/pci/voyagervar.h>
#include "opt_voyager.h"
#include "voyagerfb.h"
#include "pwmclock.h"
#ifdef VOYAGER_DEBUG
#define DPRINTF aprint_normal
#else
#define DPRINTF while (0) printf
#endif
/* interrupt stuff */
struct voyager_intr {
int (*vih_func)(void *);
void *vih_arg;
struct evcnt vih_count;
char vih_name[32];
};
struct voyager_softc {
device_t sc_dev;
pci_chipset_tag_t sc_pc;
pcitag_t sc_pcitag;
bus_space_tag_t sc_memt;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_fbh, sc_regh;
bus_addr_t sc_fb, sc_reg;
bus_size_t sc_fbsize, sc_regsize;
struct i2c_controller sc_i2c;
kmutex_t sc_i2c_lock;
/* interrupt dispatcher */
void *sc_ih;
struct voyager_intr sc_intrs[32];
};
static int voyager_match(device_t, cfdata_t, void *);
static void voyager_attach(device_t, device_t, void *);
static int voyager_print(void *, const char *);
static int voyager_intr(void *);
CFATTACH_DECL_NEW(voyager, sizeof(struct voyager_softc),
voyager_match, voyager_attach, NULL, NULL);
/* I2C glue */
static int voyager_i2c_acquire_bus(void *, int);
static void voyager_i2c_release_bus(void *, int);
static int voyager_i2c_send_start(void *, int);
static int voyager_i2c_send_stop(void *, int);
static int voyager_i2c_initiate_xfer(void *, i2c_addr_t, int);
static int voyager_i2c_read_byte(void *, uint8_t *, int);
static int voyager_i2c_write_byte(void *, uint8_t, int);
/* I2C bitbang glue */
static void voyager_i2cbb_set_bits(void *, uint32_t);
static void voyager_i2cbb_set_dir(void *, uint32_t);
static uint32_t voyager_i2cbb_read(void *);
static const struct i2c_bitbang_ops voyager_i2cbb_ops = {
voyager_i2cbb_set_bits,
voyager_i2cbb_set_dir,
voyager_i2cbb_read,
{
1 << 13,
1 << 6,
1 << 13,
0
}
};
#define GPIO_I2C_BITS ((1 << 6) | (1 << 13))
#ifdef VOYAGER_DEBUG
static void voyager_print_pwm(struct voyager_softc *, int);
#endif
static int
voyager_match(device_t parent, cfdata_t match, void *aux)
{
struct pci_attach_args *pa = (struct pci_attach_args *)aux;
if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY)
return 0;
if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_SILMOTION)
return 0;
/* only chip tested on so far - may need a list */
if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_SILMOTION_SM502)
return 100;
return (0);
}
static void
voyager_attach(device_t parent, device_t self, void *aux)
{
struct voyager_softc *sc = device_private(self);
struct pci_attach_args *pa = aux;
pci_intr_handle_t ih;
struct voyager_attach_args vaa;
struct i2cbus_attach_args iba;
uint32_t reg;
const char *intrstr;
int i;
sc->sc_pc = pa->pa_pc;
sc->sc_pcitag = pa->pa_tag;
sc->sc_memt = pa->pa_memt;
sc->sc_iot = pa->pa_iot;
sc->sc_dev = self;
pci_aprint_devinfo(pa, NULL);
if (pci_mapreg_map(pa, 0x14, PCI_MAPREG_TYPE_MEM, 0,
&sc->sc_memt, &sc->sc_regh, &sc->sc_reg, &sc->sc_regsize)) {
aprint_error("%s: failed to map registers.\n",
device_xname(sc->sc_dev));
}
if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_MEM,
BUS_SPACE_MAP_LINEAR,
&sc->sc_memt, &sc->sc_fbh, &sc->sc_fb, &sc->sc_fbsize)) {
aprint_error("%s: failed to map the frame buffer.\n",
device_xname(sc->sc_dev));
}
/* disable all interrupts */
bus_space_write_4(sc->sc_memt, sc->sc_regh, SM502_INTR_MASK, 0);
/* initialize handler list */
for (i = 0; i < 32; i++) {
sc->sc_intrs[i].vih_func = NULL;
snprintf(sc->sc_intrs[i].vih_name, 32, "int %d", i);
evcnt_attach_dynamic(&sc->sc_intrs[i].vih_count,
EVCNT_TYPE_INTR, NULL, "voyager", sc->sc_intrs[i].vih_name);
}
/* Map and establish the interrupt. */
if (pci_intr_map(pa, &ih)) {
aprint_error_dev(self, "couldn't map interrupt\n");
return;
}
intrstr = pci_intr_string(sc->sc_pc, ih);
sc->sc_ih = pci_intr_establish(sc->sc_pc, ih, IPL_AUDIO, voyager_intr, sc);
if (sc->sc_ih == NULL) {
aprint_error_dev(self, "couldn't establish interrupt");
if (intrstr != NULL)
aprint_error(" at %s", intrstr);
aprint_error("\n");
return;
}
aprint_normal_dev(self, "interrupting at %s\n", intrstr);
#ifdef VOYAGER_DEBUG
voyager_print_pwm(sc, SM502_PWM0);
voyager_print_pwm(sc, SM502_PWM1);
voyager_print_pwm(sc, SM502_PWM2);
#endif
/* attach the framebuffer driver */
vaa.vaa_memh = sc->sc_fbh;
vaa.vaa_mem_pa = sc->sc_fb;
vaa.vaa_regh = sc->sc_regh;
vaa.vaa_reg_pa = sc->sc_reg;
vaa.vaa_tag = sc->sc_memt;
vaa.vaa_pc = sc->sc_pc;
vaa.vaa_pcitag = sc->sc_pcitag;
#if NVOYAGERFB > 0
strcpy(vaa.vaa_name, "voyagerfb");
config_found_ia(sc->sc_dev, "voyagerbus", &vaa, voyager_print);
#endif
#if NPWMCLOCK > 0
strcpy(vaa.vaa_name, "pwmclock");
config_found_ia(sc->sc_dev, "voyagerbus", &vaa, voyager_print);
#endif
#ifdef notyet
strcpy(vaa.vaa_name, "vac");
config_found_ia(sc->sc_dev, "voyagerbus", &vaa, voyager_print);
#endif
/* we use this mutex wether there's an i2c bus or not */
mutex_init(&sc->sc_i2c_lock, MUTEX_DEFAULT, IPL_NONE);
/*
* see if the i2c pins are configured as gpio and if so, use them
* should probably be a compile time option
*/
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_GPIO0_CONTROL);
if ((reg & GPIO_I2C_BITS) == 0) {
/* both bits as outputs */
voyager_gpio_dir(sc, 0xffffffff, GPIO_I2C_BITS);
/* Fill in the i2c tag */
sc->sc_i2c.ic_cookie = sc;
sc->sc_i2c.ic_acquire_bus = voyager_i2c_acquire_bus;
sc->sc_i2c.ic_release_bus = voyager_i2c_release_bus;
sc->sc_i2c.ic_send_start = voyager_i2c_send_start;
sc->sc_i2c.ic_send_stop = voyager_i2c_send_stop;
sc->sc_i2c.ic_initiate_xfer = voyager_i2c_initiate_xfer;
sc->sc_i2c.ic_read_byte = voyager_i2c_read_byte;
sc->sc_i2c.ic_write_byte = voyager_i2c_write_byte;
sc->sc_i2c.ic_exec = NULL;
iba.iba_tag = &sc->sc_i2c;
config_found_ia(self, "i2cbus", &iba, iicbus_print);
}
voyager_control_gpio(sc, ~(1 << 16), 0);
voyager_gpio_dir(sc, 0xffffffff, 1 << 16);
voyager_write_gpio(sc, 0xffffffff, 1 << 16);
}
static int
voyager_print(void *aux, const char *what)
{
/*struct voyager_attach_args *vaa = aux;*/
if (what == NULL)
return 0;
printf("%s:", what);
return 0;
}
static void
voyager_i2cbb_set_bits(void *cookie, uint32_t bits)
{
struct voyager_softc *sc = cookie;
uint32_t reg;
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_GPIO_DATA0);
reg &= ~GPIO_I2C_BITS;
reg |= bits;
bus_space_write_4(sc->sc_memt, sc->sc_regh, SM502_GPIO_DATA0, reg);
}
static void
voyager_i2cbb_set_dir(void *cookie, uint32_t bits)
{
struct voyager_softc *sc = cookie;
uint32_t reg;
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_GPIO_DIR0);
reg &= ~(1 << 13);
reg |= bits;
bus_space_write_4(sc->sc_memt, sc->sc_regh, SM502_GPIO_DIR0, reg);
}
static uint32_t
voyager_i2cbb_read(void *cookie)
{
struct voyager_softc *sc = cookie;
uint32_t reg;
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_GPIO_DATA0);
return reg;
}
/* higher level I2C stuff */
static int
voyager_i2c_acquire_bus(void *cookie, int flags)
{
struct voyager_softc *sc = cookie;
mutex_enter(&sc->sc_i2c_lock);
return 0;
}
static void
voyager_i2c_release_bus(void *cookie, int flags)
{
struct voyager_softc *sc = cookie;
mutex_exit(&sc->sc_i2c_lock);
}
static int
voyager_i2c_send_start(void *cookie, int flags)
{
return (i2c_bitbang_send_start(cookie, flags, &voyager_i2cbb_ops));
}
static int
voyager_i2c_send_stop(void *cookie, int flags)
{
return (i2c_bitbang_send_stop(cookie, flags, &voyager_i2cbb_ops));
}
static int
voyager_i2c_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
{
/*
* for some reason i2c_bitbang_initiate_xfer left-shifts
* the I2C-address and then sets the direction bit
*/
return (i2c_bitbang_initiate_xfer(cookie, addr, flags,
&voyager_i2cbb_ops));
}
static int
voyager_i2c_read_byte(void *cookie, uint8_t *valp, int flags)
{
int ret;
ret = i2c_bitbang_read_byte(cookie, valp, flags, &voyager_i2cbb_ops);
return ret;
}
static int
voyager_i2c_write_byte(void *cookie, uint8_t val, int flags)
{
int ret;
ret = i2c_bitbang_write_byte(cookie, val, flags, &voyager_i2cbb_ops);
delay(500);
return ret;
}
void
voyager_twiddle_bits(void *cookie, int regnum, uint32_t mask, uint32_t bits)
{
struct voyager_softc *sc = cookie;
uint32_t reg;
/* don't interfere with i2c ops */
mutex_enter(&sc->sc_i2c_lock);
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, regnum);
reg &= mask;
reg |= bits;
bus_space_write_4(sc->sc_memt, sc->sc_regh, regnum, reg);
mutex_exit(&sc->sc_i2c_lock);
}
static int
voyager_intr(void *cookie)
{
struct voyager_softc *sc = cookie;
struct voyager_intr *ih;
uint32_t intrs;
uint32_t mask, bit;
int num;
intrs = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_INTR_STATUS);
mask = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_INTR_MASK);
intrs &= mask;
while (intrs != 0) {
num = ffs32(intrs) - 1;
bit = 1 << num;
intrs &= ~bit;
ih = &sc->sc_intrs[num];
if (ih->vih_func != NULL) {
ih->vih_func(ih->vih_arg);
}
ih->vih_count.ev_count++;
}
return 0;
}
void *
voyager_establish_intr(device_t dev, int bit, int (*handler)(void *), void *arg)
{
struct voyager_softc *sc = device_private(dev);
struct voyager_intr *ih;
uint32_t reg;
if ((bit < 0) || (bit > 31)) {
aprint_error_dev(dev, "bogus interrupt %d\n", bit);
return NULL;
}
ih = &sc->sc_intrs[bit];
if (ih->vih_func != NULL) {
aprint_error_dev(dev, "interrupt %d is already in use\n", bit);
return NULL;
}
ih->vih_func = handler;
ih->vih_arg = arg;
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, SM502_INTR_MASK);
reg |= 1 << bit;
bus_space_write_4(sc->sc_memt, sc->sc_regh, SM502_INTR_MASK, reg);
return (void *)(uintptr_t)(0x80000000 | bit);
}
void
voyager_disestablish_intr(device_t dev, void *ih)
{
}
/* timer */
#ifdef VOYAGER_DEBUG
static void
voyager_print_pwm(struct voyager_softc *sc, int pwmreg)
{
uint32_t reg;
reg = bus_space_read_4(sc->sc_memt, sc->sc_regh, pwmreg);
aprint_debug_dev(sc->sc_dev, "%08x: %08x = %d Hz, %d high, %d low\n",
pwmreg, reg,
96000000 / (1 << ((reg & SM502_PWM_CLOCK_DIV_MASK) >> SM502_PWM_CLOCK_DIV_SHIFT)),
(reg & SM502_PWM_CLOCK_HIGH_MASK) >> SM502_PWM_CLOCK_HIGH_SHIFT,
(reg & SM502_PWM_CLOCK_LOW_MASK) >> SM502_PWM_CLOCK_LOW_SHIFT);
}
#endif
uint32_t
voyager_set_pwm(int freq, int duty_cycle)
{
int ifreq, factor, bit, steps;
uint32_t reg = 0, hi, lo;
/*
* find the smallest divider that gets us within 4096 steps of the
* target frequency
*/
ifreq = freq * 4096;
factor = 96000000 / ifreq;
bit = fls32(factor);
factor = 1 << bit;
steps = 96000000 / (factor * freq);
/* can't have it all off */
if (duty_cycle < 1)
duty_cycle = 1;
/* can't be always on either */
if (duty_cycle > 999)
duty_cycle = 999;
hi = steps * duty_cycle / 1000;
if (hi < 1)
hi = 1;
lo = steps - hi;
if (lo < 1) {
hi = steps - 1;
lo = 1;
}
DPRINTF("%d hz -> %d, %d, %d / %d\n", freq, factor, steps, lo, hi);
reg = ((hi - 1) & 0xfff) << SM502_PWM_CLOCK_HIGH_SHIFT;
reg |= ((lo - 1) & 0xfff) << SM502_PWM_CLOCK_LOW_SHIFT;
reg |= (bit & 0xf) << SM502_PWM_CLOCK_DIV_SHIFT;
DPRINTF("reg: %08x\n", reg);
return reg;
}