NetBSD-5.0.2/sys/dev/onewire/onewire.c

Compare this file to the similar file:
Show the results in this format:

/* $NetBSD: onewire.c,v 1.9 2008/05/05 13:58:58 xtraeme Exp $ */
/*	$OpenBSD: onewire.c,v 1.1 2006/03/04 16:27:03 grange Exp $	*/

/*
 * Copyright (c) 2006 Alexander Yurchenko <grange@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: onewire.c,v 1.9 2008/05/05 13:58:58 xtraeme Exp $");

/*
 * 1-Wire bus driver.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/rwlock.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/queue.h>

#include <dev/onewire/onewirereg.h>
#include <dev/onewire/onewirevar.h>

#ifdef ONEWIRE_DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif

//#define ONEWIRE_MAXDEVS		256
#define ONEWIRE_MAXDEVS		8
#define ONEWIRE_SCANTIME	3

struct onewire_softc {
	device_t			sc_dev;

	struct onewire_bus *		sc_bus;
	krwlock_t			sc_rwlock;
	struct lwp *			sc_thread;
	TAILQ_HEAD(, onewire_device)	sc_devs;

	int				sc_dying;
};

struct onewire_device {
	TAILQ_ENTRY(onewire_device)	d_list;
	device_t			d_dev;
	u_int64_t			d_rom;
	int				d_present;
};

static int	onewire_match(device_t, cfdata_t, void *);
static void	onewire_attach(device_t, device_t, void *);
static int	onewire_detach(device_t, int);
static int	onewire_activate(device_t, enum devact);
int		onewire_print(void *, const char *);

static void	onewire_thread(void *);
static void	onewire_scan(struct onewire_softc *);

CFATTACH_DECL_NEW(onewire, sizeof(struct onewire_softc),
	onewire_match, onewire_attach, onewire_detach, onewire_activate);

const struct cdevsw onewire_cdevsw = {
	noopen, noclose, noread, nowrite, noioctl, nostop, notty,
	nopoll, nommap, nokqfilter, D_OTHER,
};

extern struct cfdriver onewire_cd;

static int
onewire_match(device_t parent, cfdata_t cf, void *aux)
{
	return 1;
}

static void
onewire_attach(device_t parent, device_t self, void *aux)
{
	struct onewire_softc *sc = device_private(self);
	struct onewirebus_attach_args *oba = aux;

	sc->sc_dev = self;
	sc->sc_bus = oba->oba_bus;
	rw_init(&sc->sc_rwlock);
	TAILQ_INIT(&sc->sc_devs);

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

	if (kthread_create(PRI_NONE, 0, NULL, onewire_thread, sc,
	    &sc->sc_thread, "%s", device_xname(self)) != 0)
		aprint_error_dev(self, "can't create kernel thread\n");
}

static int
onewire_detach(device_t self, int flags)
{
	struct onewire_softc *sc = device_private(self);
	int rv;

	sc->sc_dying = 1;
	if (sc->sc_thread != NULL) {
		wakeup(sc->sc_thread);
		tsleep(&sc->sc_dying, PWAIT, "owdt", 0);
	}

	onewire_lock(sc);
	//rv = config_detach_children(self, flags);
	rv = 0;  /* XXX riz */
	onewire_unlock(sc);
	rw_destroy(&sc->sc_rwlock);

	return rv;
}

static int
onewire_activate(device_t self, enum devact act)
{
	struct onewire_softc *sc = device_private(self);
	int rv = 0;

	switch (act) {
	case DVACT_ACTIVATE:
		rv = EOPNOTSUPP;
		break;
	case DVACT_DEACTIVATE:
		sc->sc_dying = 1;
		break;
	}

	//return (config_activate_children(self, act));
	return rv;
}

int
onewire_print(void *aux, const char *pnp)
{
	struct onewire_attach_args *oa = aux;
	const char *famname;

	if (pnp == NULL)
		aprint_normal(" ");

	famname = onewire_famname(ONEWIRE_ROM_FAMILY_TYPE(oa->oa_rom));
	if (famname == NULL)
		aprint_normal("family 0x%02x",
		    (uint)ONEWIRE_ROM_FAMILY_TYPE(oa->oa_rom));
	else
		aprint_normal("\"%s\"", famname);
	aprint_normal(" sn %012llx", ONEWIRE_ROM_SN(oa->oa_rom));

	if (pnp != NULL)
		aprint_normal(" at %s", pnp);

	return UNCONF;
}

int
onewirebus_print(void *aux, const char *pnp)
{
	if (pnp != NULL)
		aprint_normal("onewire at %s", pnp);

	return UNCONF;
}

void
onewire_lock(void *arg)
{
	struct onewire_softc *sc = arg;

	rw_enter(&sc->sc_rwlock, RW_WRITER);
}

void
onewire_unlock(void *arg)
{
	struct onewire_softc *sc = arg;

	rw_exit(&sc->sc_rwlock);
}

int
onewire_reset(void *arg)
{
	struct onewire_softc *sc = arg;
	struct onewire_bus *bus = sc->sc_bus;

	return bus->bus_reset(bus->bus_cookie);
}

int
onewire_bit(void *arg, int value)
{
	struct onewire_softc *sc = arg;
	struct onewire_bus *bus = sc->sc_bus;

	return bus->bus_bit(bus->bus_cookie, value);
}

int
onewire_read_byte(void *arg)
{
	struct onewire_softc *sc = arg;
	struct onewire_bus *bus = sc->sc_bus;
	uint8_t value = 0;
	int i;

	if (bus->bus_read_byte != NULL)
		return bus->bus_read_byte(bus->bus_cookie);

	for (i = 0; i < 8; i++)
		value |= (bus->bus_bit(bus->bus_cookie, 1) << i);

	return value;
}

void
onewire_write_byte(void *arg, int value)
{
	struct onewire_softc *sc = arg;
	struct onewire_bus *bus = sc->sc_bus;
	int i;

	if (bus->bus_write_byte != NULL)
		return bus->bus_write_byte(bus->bus_cookie, value);

	for (i = 0; i < 8; i++)
		bus->bus_bit(bus->bus_cookie, (value >> i) & 0x1);
}

int
onewire_triplet(void *arg, int dir)
{
	struct onewire_softc *sc = arg;
	struct onewire_bus *bus = sc->sc_bus;
	int rv;

	if (bus->bus_triplet != NULL)
		return bus->bus_triplet(bus->bus_cookie, dir);

	rv = bus->bus_bit(bus->bus_cookie, 1);
	rv <<= 1;
	rv |= bus->bus_bit(bus->bus_cookie, 1);

	switch (rv) {
	case 0x0:
		bus->bus_bit(bus->bus_cookie, dir);
		break;
	case 0x1:
		bus->bus_bit(bus->bus_cookie, 0);
		break;
	default:
		bus->bus_bit(bus->bus_cookie, 1);
	}

	return rv;
}

void
onewire_read_block(void *arg, void *buf, int len)
{
	uint8_t *p = buf;

	while (len--)
		*p++ = onewire_read_byte(arg);
}

void
onewire_write_block(void *arg, const void *buf, int len)
{
	const uint8_t *p = buf;

	while (len--)
		onewire_write_byte(arg, *p++);
}

void
onewire_matchrom(void *arg, u_int64_t rom)
{
	int i;

	onewire_write_byte(arg, ONEWIRE_CMD_MATCH_ROM);
	for (i = 0; i < 8; i++)
		onewire_write_byte(arg, (rom >> (i * 8)) & 0xff);
}

static void
onewire_thread(void *arg)
{
	struct onewire_softc *sc = arg;

	while (!sc->sc_dying) {
		onewire_scan(sc);
		tsleep(sc->sc_thread, PWAIT, "owidle", ONEWIRE_SCANTIME * hz);
	}

	sc->sc_thread = NULL;
	wakeup(&sc->sc_dying);
	kthread_exit(0);
}

static void
onewire_scan(struct onewire_softc *sc)
{
	struct onewire_device *d, *next, *nd;
	struct onewire_attach_args oa;
	struct device *dev;
	int search = 1, count = 0, present;
	int dir, rv;
	uint64_t mask, rom = 0, lastrom;
	uint8_t data[8];
	int i, i0 = -1, lastd = -1;

	TAILQ_FOREACH(d, &sc->sc_devs, d_list)
		d->d_present = 0;

	while (search && count++ < ONEWIRE_MAXDEVS) {
		/* XXX: yield processor */
		tsleep(sc, PWAIT, "owscan", hz / 10);

		/*
		 * Reset the bus. If there's no presence pulse
		 * don't search for any devices.
		 */
		onewire_lock(sc);
		if (onewire_reset(sc) != 0) {
			DPRINTF(("%s: scan: no presence pulse\n",
			    device_xname(sc->sc_dev)));
			onewire_unlock(sc);
			break;
		}

		/*
		 * Start new search. Go through the previous path to
		 * the point we made a decision last time and make an
		 * opposite decision. If we didn't make any decision
		 * stop searching.
		 */
		search = 0;
		lastrom = rom;
		rom = 0;
		onewire_write_byte(sc, ONEWIRE_CMD_SEARCH_ROM);
		for (i = 0,i0 = -1; i < 64; i++) {
			dir = (lastrom >> i) & 0x1;
			if (i == lastd)
				dir = 1;
			else if (i > lastd)
				dir = 0;
			rv = onewire_triplet(sc, dir);
			switch (rv) {
			case 0x0:
				if (i != lastd) {
					if (dir == 0)
						i0 = i;
					search = 1;
				}
				mask = dir;
				break;
			case 0x1:
				mask = 0;
				break;
			case 0x2:
				mask = 1;
				break;
			default:
				DPRINTF(("%s: scan: triplet error 0x%x, "
				    "step %d\n",
				    device_xname(sc->sc_dev), rv, i));
				onewire_unlock(sc);
				return;
			}
			rom |= (mask << i);
		}
		lastd = i0;
		onewire_unlock(sc);

		if (rom == 0)
			continue;

		/*
		 * The last byte of the ROM code contains a CRC calculated
		 * from the first 7 bytes. Re-calculate it to make sure
		 * we found a valid device.
		 */
		for (i = 0; i < 8; i++)
			data[i] = (rom >> (i * 8)) & 0xff;
		if (onewire_crc(data, 7) != data[7])
			continue;

		/*
		 * Go through the list of attached devices to see if we
		 * found a new one.
		 */
		present = 0;
		TAILQ_FOREACH(d, &sc->sc_devs, d_list) {
			if (d->d_rom == rom) {
				d->d_present = 1;
				present = 1;
				break;
			}
		}
		if (!present) {
			bzero(&oa, sizeof(oa));
			oa.oa_onewire = sc;
			oa.oa_rom = rom;
			if ((dev = config_found(sc->sc_dev, &oa,
			    onewire_print)) == NULL)
				continue;

			MALLOC(nd, struct onewire_device *,
			    sizeof(struct onewire_device), M_DEVBUF, M_NOWAIT);
			if (nd == NULL)
				continue;
			nd->d_dev = dev;
			nd->d_rom = rom;
			nd->d_present = 1;
			TAILQ_INSERT_TAIL(&sc->sc_devs, nd, d_list);
		}
	}

	/* Detach disappeared devices */
	onewire_lock(sc);
	for (d = TAILQ_FIRST(&sc->sc_devs);
	    d != NULL; d = next) {
		next = TAILQ_NEXT(d, d_list);
		if (!d->d_present) {
			config_detach(d->d_dev, DETACH_FORCE);
			TAILQ_REMOVE(&sc->sc_devs, d, d_list);
			FREE(d, M_DEVBUF);
		}
	}
	onewire_unlock(sc);
}