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