/* $Id: bbc_i2c.c,v 1.2 2001/04/02 09:59:08 davem Exp $ * bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III * platforms. * * Copyright (C) 2001 David S. Miller (davem@redhat.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include "bbc_i2c.h" /* Convert this driver to use i2c bus layer someday... */ #define I2C_PCF_PIN 0x80 #define I2C_PCF_ESO 0x40 #define I2C_PCF_ES1 0x20 #define I2C_PCF_ES2 0x10 #define I2C_PCF_ENI 0x08 #define I2C_PCF_STA 0x04 #define I2C_PCF_STO 0x02 #define I2C_PCF_ACK 0x01 #define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK) #define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) #define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) #define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) #define I2C_PCF_INI 0x40 /* 1 if not initialized */ #define I2C_PCF_STS 0x20 #define I2C_PCF_BER 0x10 #define I2C_PCF_AD0 0x08 #define I2C_PCF_LRB 0x08 #define I2C_PCF_AAS 0x04 #define I2C_PCF_LAB 0x02 #define I2C_PCF_BB 0x01 /* The BBC devices have two I2C controllers. The first I2C controller * connects mainly to configuration proms (NVRAM, cpu configuration, * dimm types, etc.). Whereas the second I2C controller connects to * environmental control devices such as fans and temperature sensors. * The second controller also connects to the smartcard reader, if present. */ #define NUM_CHILDREN 8 struct bbc_i2c_bus { struct bbc_i2c_bus *next; int index; spinlock_t lock; void *i2c_bussel_reg; void *i2c_control_regs; unsigned char own, clock; wait_queue_head_t wq; volatile int waiting; struct linux_ebus_device *bus_edev; struct { struct linux_ebus_child *device; int client_claimed; } devs[NUM_CHILDREN]; }; static struct bbc_i2c_bus *all_bbc_i2c; struct bbc_i2c_client { struct bbc_i2c_bus *bp; struct linux_ebus_child *echild; int bus; int address; }; static int find_device(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild) { int i; for (i = 0; i < NUM_CHILDREN; i++) { if (bp->devs[i].device == echild) { if (bp->devs[i].client_claimed) return 0; return 1; } } return 0; } static void set_device_claimage(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild, int val) { int i; for (i = 0; i < NUM_CHILDREN; i++) { if (bp->devs[i].device == echild) { bp->devs[i].client_claimed = val; return; } } } #define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1) #define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0) static struct bbc_i2c_bus *find_bus_for_device(struct linux_ebus_child *echild) { struct bbc_i2c_bus *bp = all_bbc_i2c; while (bp != NULL) { if (find_device(bp, echild) != 0) break; bp = bp->next; } return bp; } struct linux_ebus_child *bbc_i2c_getdev(int index) { struct bbc_i2c_bus *bp = all_bbc_i2c; struct linux_ebus_child *echild = NULL; int curidx = 0; while (bp != NULL) { struct bbc_i2c_bus *next = bp->next; int i; for (i = 0; i < NUM_CHILDREN; i++) { if (!(echild = bp->devs[i].device)) break; if (curidx == index) goto out; echild = NULL; curidx++; } bp = next; } out: if (curidx == index) return echild; return NULL; } struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *echild) { struct bbc_i2c_bus *bp = find_bus_for_device(echild); struct bbc_i2c_client *client; if (!bp) return NULL; client = kmalloc(sizeof(*client), GFP_KERNEL); if (!client) return NULL; memset(client, 0, sizeof(*client)); client->bp = bp; client->echild = echild; client->bus = echild->resource[0].start; client->address = echild->resource[1].start; claim_device(bp, echild); return client; } void bbc_i2c_detach(struct bbc_i2c_client *client) { struct bbc_i2c_bus *bp = client->bp; struct linux_ebus_child *echild = client->echild; release_device(bp, echild); kfree(client); } static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status) { DECLARE_WAITQUEUE(wait, current); int limit = 32; int ret = 1; bp->waiting = 1; add_wait_queue(&bp->wq, &wait); while (limit-- > 0) { u8 val; current->state = TASK_INTERRUPTIBLE; *status = val = readb(bp->i2c_control_regs + 0); if ((val & I2C_PCF_PIN) == 0) { ret = 0; break; } schedule_timeout(HZ/4); } remove_wait_queue(&bp->wq, &wait); bp->waiting = 0; current->state = TASK_RUNNING; return ret; } int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off) { struct bbc_i2c_bus *bp = client->bp; int address = client->address; u8 status; int ret = -1; if (bp->i2c_bussel_reg != NULL) writeb(client->bus, bp->i2c_bussel_reg); writeb(address, bp->i2c_control_regs + 0x1); writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); if (wait_for_pin(bp, &status)) goto out; writeb(off, bp->i2c_control_regs + 0x1); if (wait_for_pin(bp, &status) || (status & I2C_PCF_LRB) != 0) goto out; writeb(val, bp->i2c_control_regs + 0x1); if (wait_for_pin(bp, &status)) goto out; ret = 0; out: writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); return ret; } int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off) { struct bbc_i2c_bus *bp = client->bp; unsigned char address = client->address, status; int ret = -1; if (bp->i2c_bussel_reg != NULL) writeb(client->bus, bp->i2c_bussel_reg); writeb(address, bp->i2c_control_regs + 0x1); writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); if (wait_for_pin(bp, &status)) goto out; writeb(off, bp->i2c_control_regs + 0x1); if (wait_for_pin(bp, &status) || (status & I2C_PCF_LRB) != 0) goto out; writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); address |= 0x1; /* READ */ writeb(address, bp->i2c_control_regs + 0x1); writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); if (wait_for_pin(bp, &status)) goto out; /* Set PIN back to one so the device sends the first * byte. */ (void) readb(bp->i2c_control_regs + 0x1); if (wait_for_pin(bp, &status)) goto out; writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0); *byte = readb(bp->i2c_control_regs + 0x1); if (wait_for_pin(bp, &status)) goto out; ret = 0; out: writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); (void) readb(bp->i2c_control_regs + 0x1); return ret; } int bbc_i2c_write_buf(struct bbc_i2c_client *client, char *buf, int len, int off) { int ret = 0; while (len > 0) { int err = bbc_i2c_writeb(client, *buf, off); if (err < 0) { ret = err; break; } len--; buf++; off++; } return ret; } int bbc_i2c_read_buf(struct bbc_i2c_client *client, char *buf, int len, int off) { int ret = 0; while (len > 0) { int err = bbc_i2c_readb(client, buf, off); if (err < 0) { ret = err; break; } len--; buf++; off++; } return ret; } EXPORT_SYMBOL(bbc_i2c_getdev); EXPORT_SYMBOL(bbc_i2c_attach); EXPORT_SYMBOL(bbc_i2c_detach); EXPORT_SYMBOL(bbc_i2c_writeb); EXPORT_SYMBOL(bbc_i2c_readb); EXPORT_SYMBOL(bbc_i2c_write_buf); EXPORT_SYMBOL(bbc_i2c_read_buf); static void bbc_i2c_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct bbc_i2c_bus *bp = dev_id; /* PIN going from set to clear is the only event which * makes the i2c assert an interrupt. */ if (bp->waiting && !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN)) wake_up(&bp->wq); } static void __init reset_one_i2c(struct bbc_i2c_bus *bp) { writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); writeb(bp->own, bp->i2c_control_regs + 0x1); writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); writeb(bp->clock, bp->i2c_control_regs + 0x1); writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0); } static int __init attach_one_i2c(struct linux_ebus_device *edev, int index) { struct bbc_i2c_bus *bp = kmalloc(sizeof(*bp), GFP_KERNEL); struct linux_ebus_child *echild; int entry; if (!bp) return -ENOMEM; memset(bp, 0, sizeof(*bp)); bp->i2c_control_regs = ioremap(edev->resource[0].start, 0x2); if (!bp->i2c_control_regs) goto fail; if (edev->num_addrs == 2) { bp->i2c_bussel_reg = ioremap(edev->resource[1].start, 0x1); if (!bp->i2c_bussel_reg) goto fail; } bp->waiting = 0; init_waitqueue_head(&bp->wq); if (request_irq(edev->irqs[0], bbc_i2c_interrupt, SA_SHIRQ, "bbc_i2c", bp)) goto fail; bp->index = index; bp->bus_edev = edev; spin_lock_init(&bp->lock); bp->next = all_bbc_i2c; all_bbc_i2c = bp; entry = 0; for (echild = edev->children; echild && entry < 8; echild = echild->next, entry++) { bp->devs[entry].device = echild; bp->devs[entry].client_claimed = 0; } writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); bp->own = readb(bp->i2c_control_regs + 0x01); writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); bp->clock = readb(bp->i2c_control_regs + 0x01); printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n", bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock); reset_one_i2c(bp); return 0; fail: if (bp->i2c_bussel_reg) iounmap(bp->i2c_bussel_reg); if (bp->i2c_control_regs) iounmap(bp->i2c_control_regs); kfree(bp); return -EINVAL; } static int __init bbc_present(void) { struct linux_ebus *ebus = NULL; struct linux_ebus_device *edev = NULL; for_each_ebus(ebus) { for_each_ebusdev(edev, ebus) { if (!strcmp(edev->prom_name, "bbc")) return 1; } } return 0; } extern void bbc_envctrl_init(void); extern void bbc_envctrl_cleanup(void); static int __init bbc_i2c_init(void) { struct linux_ebus *ebus = NULL; struct linux_ebus_device *edev = NULL; int index = 0; if (tlb_type != cheetah || !bbc_present()) return -ENODEV; for_each_ebus(ebus) { for_each_ebusdev(edev, ebus) { if (!strcmp(edev->prom_name, "i2c")) { if (!attach_one_i2c(edev, index)) index++; } } } if (!index) return -ENODEV; bbc_envctrl_init(); return 0; } static void __exit bbc_i2c_cleanup(void) { struct bbc_i2c_bus *bp = all_bbc_i2c; bbc_envctrl_cleanup(); while (bp != NULL) { struct bbc_i2c_bus *next = bp->next; free_irq(bp->bus_edev->irqs[0], bp); if (bp->i2c_bussel_reg) iounmap(bp->i2c_bussel_reg); if (bp->i2c_control_regs) iounmap(bp->i2c_control_regs); kfree(bp); bp = next; } all_bbc_i2c = NULL; } module_init(bbc_i2c_init); module_exit(bbc_i2c_cleanup);