iicbb.c revision 161516
140782Snsouch/*-
293023Snsouch * Copyright (c) 1998, 2001 Nicolas Souchu
340782Snsouch * All rights reserved.
440782Snsouch *
540782Snsouch * Redistribution and use in source and binary forms, with or without
640782Snsouch * modification, are permitted provided that the following conditions
740782Snsouch * are met:
840782Snsouch * 1. Redistributions of source code must retain the above copyright
940782Snsouch *    notice, this list of conditions and the following disclaimer.
1040782Snsouch * 2. Redistributions in binary form must reproduce the above copyright
1140782Snsouch *    notice, this list of conditions and the following disclaimer in the
1240782Snsouch *    documentation and/or other materials provided with the distribution.
1340782Snsouch *
1440782Snsouch * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1540782Snsouch * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1640782Snsouch * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1740782Snsouch * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1840782Snsouch * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1940782Snsouch * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2040782Snsouch * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2140782Snsouch * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2240782Snsouch * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2340782Snsouch * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2440782Snsouch * SUCH DAMAGE.
2540782Snsouch */
2640782Snsouch
27119418Sobrien#include <sys/cdefs.h>
28119418Sobrien__FBSDID("$FreeBSD: head/sys/dev/iicbus/iicbb.c 161516 2006-08-21 17:32:50Z imp $");
29119418Sobrien
3040782Snsouch/*
3140782Snsouch * Generic I2C bit-banging code
3240782Snsouch *
3340782Snsouch * Example:
3440782Snsouch *
3540782Snsouch *	iicbus
3640782Snsouch *	 /  \
3740782Snsouch *    iicbb pcf
3840782Snsouch *     |  \
3940782Snsouch *   bti2c lpbb
4040782Snsouch *
4140782Snsouch * From Linux I2C generic interface
4240782Snsouch * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
4340782Snsouch *
4440782Snsouch */
4540782Snsouch
4640782Snsouch#include <sys/param.h>
4740782Snsouch#include <sys/kernel.h>
4840782Snsouch#include <sys/systm.h>
4940782Snsouch#include <sys/module.h>
5040782Snsouch#include <sys/bus.h>
5140782Snsouch#include <sys/uio.h>
5240782Snsouch
5340782Snsouch
5440782Snsouch#include <dev/iicbus/iiconf.h>
5540782Snsouch#include <dev/iicbus/iicbus.h>
5640782Snsouch
5740782Snsouch#include <dev/smbus/smbconf.h>
5840782Snsouch
5940782Snsouch#include "iicbus_if.h"
6040782Snsouch#include "iicbb_if.h"
6140782Snsouch
6240782Snsouchstruct iicbb_softc {
6393023Snsouch	device_t iicbus;
6440782Snsouch};
6540782Snsouch
6640782Snsouchstatic int iicbb_probe(device_t);
6740782Snsouchstatic int iicbb_attach(device_t);
6893023Snsouchstatic int iicbb_detach(device_t);
6949195Smdoddstatic int iicbb_print_child(device_t, device_t);
7040782Snsouch
7140782Snsouchstatic int iicbb_callback(device_t, int, caddr_t);
7240782Snsouchstatic int iicbb_start(device_t, u_char, int);
7340782Snsouchstatic int iicbb_stop(device_t);
7440782Snsouchstatic int iicbb_write(device_t, char *, int, int *, int);
7540782Snsouchstatic int iicbb_read(device_t, char *, int, int *, int, int);
7640782Snsouchstatic int iicbb_reset(device_t, u_char, u_char, u_char *);
7740782Snsouch
7840782Snsouchstatic device_method_t iicbb_methods[] = {
7940782Snsouch	/* device interface */
8040782Snsouch	DEVMETHOD(device_probe,		iicbb_probe),
8140782Snsouch	DEVMETHOD(device_attach,	iicbb_attach),
8293023Snsouch	DEVMETHOD(device_detach,	iicbb_detach),
8340782Snsouch
8440782Snsouch	/* bus interface */
8540782Snsouch	DEVMETHOD(bus_print_child,	iicbb_print_child),
8640782Snsouch
8740782Snsouch	/* iicbus interface */
8840782Snsouch	DEVMETHOD(iicbus_callback,	iicbb_callback),
8940782Snsouch	DEVMETHOD(iicbus_start,		iicbb_start),
9040782Snsouch	DEVMETHOD(iicbus_repeated_start, iicbb_start),
9140782Snsouch	DEVMETHOD(iicbus_stop,		iicbb_stop),
9240782Snsouch	DEVMETHOD(iicbus_write,		iicbb_write),
9340782Snsouch	DEVMETHOD(iicbus_read,		iicbb_read),
9440782Snsouch	DEVMETHOD(iicbus_reset,		iicbb_reset),
9540782Snsouch
9640782Snsouch	{ 0, 0 }
9740782Snsouch};
9840782Snsouch
99116559Sjmgdriver_t iicbb_driver = {
10040782Snsouch	"iicbb",
10140782Snsouch	iicbb_methods,
10240782Snsouch	sizeof(struct iicbb_softc),
10340782Snsouch};
10440782Snsouch
105116559Sjmgdevclass_t iicbb_devclass;
10640782Snsouch
107161516Simpstatic int
108161516Simpiicbb_probe(device_t dev)
10940782Snsouch{
11093023Snsouch	device_set_desc(dev, "I2C bit-banging driver");
11140782Snsouch
11240782Snsouch	return (0);
11340782Snsouch}
11440782Snsouch
115161516Simpstatic int
116161516Simpiicbb_attach(device_t dev)
11740782Snsouch{
11893023Snsouch	struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev);
11993023Snsouch
12093023Snsouch	sc->iicbus = device_add_child(dev, "iicbus", -1);
12193023Snsouch	if (!sc->iicbus)
12293023Snsouch		return (ENXIO);
12393023Snsouch	bus_generic_attach(dev);
12493023Snsouch
12540782Snsouch	return (0);
12640782Snsouch}
12740782Snsouch
128161516Simpstatic int
129161516Simpiicbb_detach(device_t dev)
13093023Snsouch{
13193023Snsouch	struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev);
13293023Snsouch
13393023Snsouch	if (sc->iicbus) {
13493023Snsouch		bus_generic_detach(dev);
13593023Snsouch		device_delete_child(dev, sc->iicbus);
13693023Snsouch	}
13793023Snsouch
13893023Snsouch	return (0);
13993023Snsouch}
14093023Snsouch
14149195Smdoddstatic int
14240782Snsouchiicbb_print_child(device_t bus, device_t dev)
14340782Snsouch{
14440782Snsouch	int error;
14549195Smdodd	int retval = 0;
14640782Snsouch	u_char oldaddr;
14740782Snsouch
14849195Smdodd	retval += bus_print_child_header(bus, dev);
14940782Snsouch	/* retrieve the interface I2C address */
15040914Snsouch	error = IICBB_RESET(device_get_parent(bus), IIC_FASTEST, 0, &oldaddr);
15140782Snsouch	if (error == IIC_ENOADDR) {
15249195Smdodd		retval += printf(" on %s master-only\n",
15349195Smdodd				 device_get_nameunit(bus));
15440782Snsouch	} else {
15540782Snsouch		/* restore the address */
15640914Snsouch		IICBB_RESET(device_get_parent(bus), IIC_FASTEST, oldaddr, NULL);
15740782Snsouch
15849195Smdodd		retval += printf(" on %s addr 0x%x\n",
15949195Smdodd				 device_get_nameunit(bus), oldaddr & 0xff);
16040782Snsouch	}
16140782Snsouch
16249195Smdodd	return (retval);
16340782Snsouch}
16440782Snsouch
16593023Snsouch#define IIC_DELAY	10
16640782Snsouch
16793023Snsouch#define I2C_SETSDA(dev,val) do {			\
16893023Snsouch	IICBB_SETSDA(device_get_parent(dev), val);	\
16993023Snsouch	DELAY(IIC_DELAY);				\
17093023Snsouch	} while (0)
17140782Snsouch
17293023Snsouch#define I2C_SETSCL(dev,val) do {			\
17393023Snsouch	iicbb_setscl(dev, val, 100);			\
17493023Snsouch	} while (0)
17593023Snsouch
17693023Snsouch#define I2C_SET(dev,ctrl,data) do {			\
17793023Snsouch	I2C_SETSCL(dev, ctrl);				\
17893023Snsouch	I2C_SETSDA(dev, data);				\
17993023Snsouch	} while (0)
18093023Snsouch
18193023Snsouch#define I2C_GETSDA(dev) (IICBB_GETSDA(device_get_parent(dev)))
18293023Snsouch
18393023Snsouch#define I2C_GETSCL(dev) (IICBB_GETSCL(device_get_parent(dev)))
18493023Snsouch
18540782Snsouchstatic int i2c_debug = 0;
18693023Snsouch#define I2C_DEBUG(x)	do {					\
18793023Snsouch				if (i2c_debug) (x);		\
18893023Snsouch			} while (0)
18940782Snsouch
19093023Snsouch#define I2C_LOG(format,args...)	do {				\
19193023Snsouch					printf(format, args);	\
19293023Snsouch				} while (0)
19393023Snsouch
194161516Simpstatic void
195161516Simpiicbb_setscl(device_t dev, int val, int timeout)
19640782Snsouch{
19793023Snsouch	int k = 0;
19893023Snsouch
19993023Snsouch	IICBB_SETSCL(device_get_parent(dev), val);
20093023Snsouch	DELAY(IIC_DELAY);
20193023Snsouch
20293023Snsouch	while (val && !I2C_GETSCL(dev) && k++ < timeout) {
20393023Snsouch		IICBB_SETSCL(device_get_parent(dev), val);
20493023Snsouch		DELAY(IIC_DELAY);
20593023Snsouch	}
20693023Snsouch
20793023Snsouch	return;
20893023Snsouch}
20993023Snsouch
210161516Simpstatic void
211161516Simpiicbb_one(device_t dev, int timeout)
21293023Snsouch{
21340782Snsouch	I2C_SET(dev,0,1);
21440782Snsouch	I2C_SET(dev,1,1);
21540782Snsouch	I2C_SET(dev,0,1);
21640782Snsouch	return;
21740782Snsouch}
21840782Snsouch
219161516Simpstatic void
220161516Simpiicbb_zero(device_t dev, int timeout)
22140782Snsouch{
22240782Snsouch	I2C_SET(dev,0,0);
22340782Snsouch	I2C_SET(dev,1,0);
22440782Snsouch	I2C_SET(dev,0,0);
22540782Snsouch	return;
22640782Snsouch}
22740782Snsouch
22840782Snsouch/*
22940782Snsouch * Waiting for ACKNOWLEDGE.
23040782Snsouch *
23140782Snsouch * When a chip is being addressed or has received data it will issue an
23240782Snsouch * ACKNOWLEDGE pulse. Therefore the MASTER must release the DATA line
23340782Snsouch * (set it to high level) and then release the CLOCK line.
23440782Snsouch * Now it must wait for the SLAVE to pull the DATA line low.
23540782Snsouch * Actually on the bus this looks like a START condition so nothing happens
23640782Snsouch * because of the fact that the IC's that have not been addressed are doing
23740782Snsouch * nothing.
23840782Snsouch *
23940782Snsouch * When the SLAVE has pulled this line low the MASTER will take the CLOCK
24040782Snsouch * line low and then the SLAVE will release the SDA (data) line.
24140782Snsouch */
242161516Simpstatic int
243161516Simpiicbb_ack(device_t dev, int timeout)
24440782Snsouch{
24540782Snsouch	int noack;
24693023Snsouch	int k = 0;
24740782Snsouch
24840782Snsouch	I2C_SET(dev,0,1);
24940782Snsouch	I2C_SET(dev,1,1);
25040782Snsouch	do {
25193023Snsouch		noack = I2C_GETSDA(dev);
25240782Snsouch		if (!noack)
25340782Snsouch			break;
25493023Snsouch		DELAY(10);
25593023Snsouch		k += 10;
25693023Snsouch	} while (k < timeout);
25740782Snsouch
25840782Snsouch	I2C_SET(dev,0,1);
25940782Snsouch	I2C_DEBUG(printf("%c ",noack?'-':'+'));
26040782Snsouch
26140782Snsouch	return (noack);
26240782Snsouch}
26340782Snsouch
264161516Simpstatic void
265161516Simpiicbb_sendbyte(device_t dev, u_char data, int timeout)
26640782Snsouch{
26740782Snsouch	int i;
26840782Snsouch
26993023Snsouch	for (i=7; i>=0; i--) {
27093023Snsouch		if (data&(1<<i)) {
27193023Snsouch			iicbb_one(dev, timeout);
27293023Snsouch		} else {
27393023Snsouch			iicbb_zero(dev, timeout);
27493023Snsouch		}
27593023Snsouch	}
27640782Snsouch	I2C_DEBUG(printf("w%02x",(int)data));
27740782Snsouch	return;
27840782Snsouch}
27940782Snsouch
280161516Simpstatic u_char
281161516Simpiicbb_readbyte(device_t dev, int last, int timeout)
28240782Snsouch{
28340782Snsouch	int i;
28440782Snsouch	unsigned char data=0;
28540782Snsouch
28640782Snsouch	I2C_SET(dev,0,1);
28740782Snsouch	for (i=7; i>=0; i--)
28840782Snsouch	{
28940782Snsouch		I2C_SET(dev,1,1);
29093023Snsouch		if (I2C_GETSDA(dev))
29140782Snsouch			data |= (1<<i);
29240782Snsouch		I2C_SET(dev,0,1);
29340782Snsouch	}
29493023Snsouch	if (last) {
29593023Snsouch		iicbb_one(dev, timeout);
29693023Snsouch	} else {
29793023Snsouch		iicbb_zero(dev, timeout);
29893023Snsouch	}
29940782Snsouch	I2C_DEBUG(printf("r%02x%c ",(int)data,last?'-':'+'));
30040782Snsouch	return data;
30140782Snsouch}
30240782Snsouch
303161516Simpstatic int
304161516Simpiicbb_callback(device_t dev, int index, caddr_t data)
30540782Snsouch{
30640782Snsouch	return (IICBB_CALLBACK(device_get_parent(dev), index, data));
30740782Snsouch}
30840782Snsouch
309161516Simpstatic int
310161516Simpiicbb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
31140782Snsouch{
31240782Snsouch	return (IICBB_RESET(device_get_parent(dev), speed, addr, oldaddr));
31340782Snsouch}
31440782Snsouch
315161516Simpstatic int
316161516Simpiicbb_start(device_t dev, u_char slave, int timeout)
31740782Snsouch{
31840782Snsouch	int error;
31940782Snsouch
32040782Snsouch	I2C_DEBUG(printf("<"));
32140782Snsouch
32240782Snsouch	I2C_SET(dev,1,1);
32340782Snsouch	I2C_SET(dev,1,0);
32440782Snsouch	I2C_SET(dev,0,0);
32540782Snsouch
32640782Snsouch	/* send address */
32793023Snsouch	iicbb_sendbyte(dev, slave, timeout);
32840782Snsouch
32940782Snsouch	/* check for ack */
33040782Snsouch	if (iicbb_ack(dev, timeout)) {
33140782Snsouch		error = IIC_ENOACK;
33240782Snsouch		goto error;
33340782Snsouch	}
33440782Snsouch
33540782Snsouch	return(0);
33640782Snsouch
33740782Snsoucherror:
33840782Snsouch	iicbb_stop(dev);
33940782Snsouch	return (error);
34040782Snsouch}
34140782Snsouch
342161516Simpstatic int
343161516Simpiicbb_stop(device_t dev)
34440782Snsouch{
34540782Snsouch	I2C_SET(dev,0,0);
34640782Snsouch	I2C_SET(dev,1,0);
34740782Snsouch	I2C_SET(dev,1,1);
34840782Snsouch	I2C_DEBUG(printf(">"));
34940782Snsouch	return (0);
35040782Snsouch}
35140782Snsouch
352161516Simpstatic int
353161516Simpiicbb_write(device_t dev, char * buf, int len, int *sent, int timeout)
35440782Snsouch{
35540782Snsouch	int bytes, error = 0;
35640782Snsouch
35740782Snsouch	bytes = 0;
35840782Snsouch	while (len) {
35940782Snsouch		/* send byte */
36093023Snsouch		iicbb_sendbyte(dev,(u_char)*buf++, timeout);
36140782Snsouch
36240782Snsouch		/* check for ack */
36340782Snsouch		if (iicbb_ack(dev, timeout)) {
36440782Snsouch			error = IIC_ENOACK;
36540782Snsouch			goto error;
36640782Snsouch		}
36740782Snsouch		bytes ++;
36840782Snsouch		len --;
36940782Snsouch	}
37040782Snsouch
37140782Snsoucherror:
37240782Snsouch	*sent = bytes;
37340782Snsouch	return (error);
37440782Snsouch}
37540782Snsouch
376161516Simpstatic int
377161516Simpiicbb_read(device_t dev, char * buf, int len, int *read, int last, int delay)
37840782Snsouch{
37940782Snsouch	int bytes;
38040782Snsouch
38140782Snsouch	bytes = 0;
38240782Snsouch	while (len) {
38340782Snsouch		/* XXX should insert delay here */
38493023Snsouch		*buf++ = (char)iicbb_readbyte(dev, (len == 1) ? last : 0, delay);
38540782Snsouch
38640782Snsouch		bytes ++;
38740782Snsouch		len --;
38840782Snsouch	}
38940782Snsouch
39040782Snsouch	*read = bytes;
39140782Snsouch	return (0);
39240782Snsouch}
39340782Snsouch
39493023SnsouchDRIVER_MODULE(iicbb, bktr, iicbb_driver, iicbb_devclass, 0, 0);
39540782SnsouchDRIVER_MODULE(iicbb, lpbb, iicbb_driver, iicbb_devclass, 0, 0);
39693023SnsouchDRIVER_MODULE(iicbb, viapm, iicbb_driver, iicbb_devclass, 0, 0);
39793023Snsouch
39893023SnsouchMODULE_DEPEND(iicbb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
39993023SnsouchMODULE_VERSION(iicbb, IICBB_MODVER);
400