Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/* linux/drivers/i2c/scx200_acb.c
Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
National Semiconductor SCx200 ACCESS.bus support
Based on i2c-keywest.c which is:
Copyright (c) 2001 Benjamin Herrenschmidt <benh@kernel.crashing.org>
Copyright (c) 2000 Philip Edelbrock <phil@stimpy.netroedge.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/smp_lock.h>
#include <linux/pci.h>
#include <asm/io.h>
#include <linux/scx200.h>
#define NAME "scx200_acb"
MODULE_AUTHOR
("Christer Weinigel <wingel@nano-system.com>");
MODULE_DESCRIPTION
("NatSemi SCx200 ACCESS.bus Driver");
MODULE_LICENSE
("GPL");
#define MAX_DEVICES 4
static int base
[MAX_DEVICES
] = { 0x840 };
MODULE_PARM
(base
, "1-4i");
MODULE_PARM_DESC
(base
, "Base addresses for the ACCESS.bus controllers");
#define DEBUG 0
#if DEBUG
#define DBG(x...) printk(KERN_DEBUG NAME ": " x)
#else
#define DBG(x...)
#endif
/* The hardware supports interrupt driven mode too, but I haven't
implemented that. */
#define POLLED_MODE 1
#define POLL_TIMEOUT (HZ)
enum scx200_acb_state
{
state_idle
,
state_address
,
state_command
,
state_repeat_start
,
state_quick
,
state_read
,
state_write
,
};
static const char *scx200_acb_state_name
[] = {
"idle",
"address",
"command",
"repeat_start",
"quick",
"read",
"write",
};
/* Physical interface */
struct scx200_acb_iface
{
struct scx200_acb_iface
*next
;
struct i2c_adapter adapter
;
unsigned base
;
struct semaphore sem
;
/* State machine data */
enum scx200_acb_state state
;
int result
;
u8 address_byte
;
u8 command
;
u8
*ptr
;
char needs_reset
;
unsigned len
;
};
/* Register Definitions */
#define ACBSDA (iface->base + 0)
#define ACBST (iface->base + 1)
#define ACBST_SDAST 0x40 /* SDA Status */
#define ACBST_BER 0x20
#define ACBST_NEGACK 0x10 /* Negative Acknowledge */
#define ACBST_STASTR 0x08 /* Stall After Start */
#define ACBST_MASTER 0x02
#define ACBCST (iface->base + 2)
#define ACBCST_BB 0x02
#define ACBCTL1 (iface->base + 3)
#define ACBCTL1_STASTRE 0x80
#define ACBCTL1_NMINTE 0x40
#define ACBCTL1_ACK 0x10
#define ACBCTL1_STOP 0x02
#define ACBCTL1_START 0x01
#define ACBADDR (iface->base + 4)
#define ACBCTL2 (iface->base + 5)
#define ACBCTL2_ENABLE 0x01
/************************************************************************/
static void scx200_acb_machine
(struct scx200_acb_iface
*iface
, u8 status
)
{
const char *errmsg
;
DBG
("state %s, status = 0x%02x\n",
scx200_acb_state_name
[iface
->state
], status
);
if (status
& ACBST_BER
) {
errmsg
= "bus error";
goto error
;
}
if (!(status
& ACBST_MASTER
)) {
errmsg
= "not master";
goto error
;
}
if (status
& ACBST_NEGACK
)
goto negack
;
switch (iface
->state
) {
case state_idle
:
dev_warn
(&iface
->adapter.
dev, "interrupt in idle state\n");
break;
case state_address
:
/* Do a pointer write first */
outb
(iface
->address_byte
& ~
1, ACBSDA
);
iface
->state
= state_command
;
break;
case state_command
:
outb
(iface
->command
, ACBSDA
);
if (iface
->address_byte
& 1)
iface
->state
= state_repeat_start
;
else
iface
->state
= state_write
;
break;
case state_repeat_start
:
outb
(inb
(ACBCTL1
) | ACBCTL1_START
, ACBCTL1
);
/* fallthrough */
case state_quick
:
if (iface
->address_byte
& 1) {
if (iface
->len
== 1)
outb
(inb
(ACBCTL1
) | ACBCTL1_ACK
, ACBCTL1
);
else
outb
(inb
(ACBCTL1
) & ~ACBCTL1_ACK
, ACBCTL1
);
outb
(iface
->address_byte
, ACBSDA
);
iface
->state
= state_read
;
} else {
outb
(iface
->address_byte
, ACBSDA
);
iface
->state
= state_write
;
}
break;
case state_read
:
/* Set ACK if receiving the last byte */
if (iface
->len
== 1)
outb
(inb
(ACBCTL1
) | ACBCTL1_ACK
, ACBCTL1
);
else
outb
(inb
(ACBCTL1
) & ~ACBCTL1_ACK
, ACBCTL1
);
*iface
->ptr
++ = inb
(ACBSDA
);
--iface
->len
;
if (iface
->len
== 0) {
iface
->result
= 0;
iface
->state
= state_idle
;
outb
(inb
(ACBCTL1
) | ACBCTL1_STOP
, ACBCTL1
);
}
break;
case state_write
:
if (iface
->len
== 0) {
iface
->result
= 0;
iface
->state
= state_idle
;
outb
(inb
(ACBCTL1
) | ACBCTL1_STOP
, ACBCTL1
);
break;
}
outb
(*iface
->ptr
++, ACBSDA
);
--iface
->len
;
break;
}
return;
negack
:
DBG
("negative acknowledge in state %s\n",
scx200_acb_state_name
[iface
->state
]);
iface
->state
= state_idle
;
iface
->result
= -ENXIO
;
outb
(inb
(ACBCTL1
) | ACBCTL1_STOP
, ACBCTL1
);
outb
(ACBST_STASTR
| ACBST_NEGACK
, ACBST
);
return;
error
:
dev_err
(&iface
->adapter.
dev, "%s in state %s\n", errmsg
,
scx200_acb_state_name
[iface
->state
]);
iface
->state
= state_idle
;
iface
->result
= -EIO
;
iface
->needs_reset
= 1;
}
static void scx200_acb_timeout
(struct scx200_acb_iface
*iface
)
{
dev_err
(&iface
->adapter.
dev, "timeout in state %s\n",
scx200_acb_state_name
[iface
->state
]);
iface
->state
= state_idle
;
iface
->result
= -EIO
;
iface
->needs_reset
= 1;
}
#ifdef POLLED_MODE
static void scx200_acb_poll
(struct scx200_acb_iface
*iface
)
{
u8 status
= 0;
unsigned long timeout
;
timeout
= jiffies
+ POLL_TIMEOUT
;
while (time_before
(jiffies
, timeout
)) {
status
= inb
(ACBST
);
if ((status
& (ACBST_SDAST
|ACBST_BER
|ACBST_NEGACK
)) != 0) {
scx200_acb_machine
(iface
, status
);
return;
}
schedule_timeout
(HZ
/100+1);
}
scx200_acb_timeout
(iface
);
}
#endif /* POLLED_MODE */
static void scx200_acb_reset
(struct scx200_acb_iface
*iface
)
{
/* Disable the ACCESS.bus device and Configure the SCL
frequency: 16 clock cycles */
outb
(0x70, ACBCTL2
);
/* Polling mode */
outb
(0, ACBCTL1
);
/* Disable slave address */
outb
(0, ACBADDR
);
/* Enable the ACCESS.bus device */
outb
(inb
(ACBCTL2
) | ACBCTL2_ENABLE
, ACBCTL2
);
/* Free STALL after START */
outb
(inb
(ACBCTL1
) & ~
(ACBCTL1_STASTRE
| ACBCTL1_NMINTE
), ACBCTL1
);
/* Send a STOP */
outb
(inb
(ACBCTL1
) | ACBCTL1_STOP
, ACBCTL1
);
/* Clear BER, NEGACK and STASTR bits */
outb
(ACBST_BER
| ACBST_NEGACK
| ACBST_STASTR
, ACBST
);
/* Clear BB bit */
outb
(inb
(ACBCST
) | ACBCST_BB
, ACBCST
);
}
static s32 scx200_acb_smbus_xfer
(struct i2c_adapter
*adapter
,
u16 address
, unsigned short flags
,
char rw
, u8 command
, int size
,
union i2c_smbus_data
*data
)
{
struct scx200_acb_iface
*iface
= i2c_get_adapdata
(adapter
);
int len
;
u8
*buffer
;
u16 cur_word
;
int rc
;
switch (size
) {
case I2C_SMBUS_QUICK
:
len
= 0;
buffer
= NULL
;
break;
case I2C_SMBUS_BYTE
:
if (rw
== I2C_SMBUS_READ
) {
len
= 1;
buffer
= &data
->byte
;
} else {
len
= 1;
buffer
= &command
;
}
break;
case I2C_SMBUS_BYTE_DATA
:
len
= 1;
buffer
= &data
->byte
;
break;
case I2C_SMBUS_WORD_DATA
:
len
= 2;
cur_word
= cpu_to_le16
(data
->word
);
buffer
= (u8
*)&cur_word
;
break;
case I2C_SMBUS_BLOCK_DATA
:
len
= data
->block
[0];
buffer
= &data
->block
[1];
break;
default:
return -EINVAL
;
}
DBG
("size=%d, address=0x%x, command=0x%x, len=%d, read=%d\n",
size
, address
, command
, len
, rw
== I2C_SMBUS_READ
);
if (!len
&& rw
== I2C_SMBUS_READ
) {
dev_warn
(&adapter
->dev
, "zero length read\n");
return -EINVAL
;
}
if (len
&& !buffer
) {
dev_warn
(&adapter
->dev
, "nonzero length but no buffer\n");
return -EFAULT
;
}
down
(&iface
->sem
);
iface
->address_byte
= address
<<1;
if (rw
== I2C_SMBUS_READ
)
iface
->address_byte
|= 1;
iface
->command
= command
;
iface
->ptr
= buffer
;
iface
->len
= len
;
iface
->result
= -EINVAL
;
iface
->needs_reset
= 0;
outb
(inb
(ACBCTL1
) | ACBCTL1_START
, ACBCTL1
);
if (size
== I2C_SMBUS_QUICK
|| size
== I2C_SMBUS_BYTE
)
iface
->state
= state_quick
;
else
iface
->state
= state_address
;
#ifdef POLLED_MODE
while (iface
->state
!= state_idle
)
scx200_acb_poll
(iface
);
#else /* POLLED_MODE */
#error Interrupt driven mode not implemented
#endif /* POLLED_MODE */
if (iface
->needs_reset
)
scx200_acb_reset
(iface
);
rc
= iface
->result
;
up
(&iface
->sem
);
if (rc
== 0 && size
== I2C_SMBUS_WORD_DATA
&& rw
== I2C_SMBUS_READ
)
data
->word
= le16_to_cpu
(cur_word
);
#if DEBUG
printk
(KERN_DEBUG NAME
": transfer done, result: %d", rc
);
if (buffer
) {
int i
;
printk
(" data:");
for (i
= 0; i
< len
; ++i
)
printk
(" %02x", buffer
[i
]);
}
printk
("\n");
#endif
return rc
;
}
static u32 scx200_acb_func
(struct i2c_adapter
*adapter
)
{
return I2C_FUNC_SMBUS_QUICK
| I2C_FUNC_SMBUS_BYTE
|
I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_WORD_DATA
|
I2C_FUNC_SMBUS_BLOCK_DATA
;
}
/* For now, we only handle combined mode (smbus) */
static struct i2c_algorithm scx200_acb_algorithm
= {
.
name = "NatSemi SCx200 ACCESS.bus",
.
id = I2C_ALGO_SMBUS
,
.
smbus_xfer = scx200_acb_smbus_xfer
,
.
functionality = scx200_acb_func
,
};
struct scx200_acb_iface
*scx200_acb_list
;
int scx200_acb_probe
(struct scx200_acb_iface
*iface
)
{
u8 val
;
/* Disable the ACCESS.bus device and Configure the SCL
frequency: 16 clock cycles */
outb
(0x70, ACBCTL2
);
if (inb
(ACBCTL2
) != 0x70) {
DBG
("ACBCTL2 readback failed\n");
return -ENXIO
;
}
outb
(inb
(ACBCTL1
) | ACBCTL1_NMINTE
, ACBCTL1
);
val
= inb
(ACBCTL1
);
if (val
) {
DBG
("disabled, but ACBCTL1=0x%02x\n", val
);
return -ENXIO
;
}
outb
(inb
(ACBCTL2
) | ACBCTL2_ENABLE
, ACBCTL2
);
outb
(inb
(ACBCTL1
) | ACBCTL1_NMINTE
, ACBCTL1
);
val
= inb
(ACBCTL1
);
if ((val
& ACBCTL1_NMINTE
) != ACBCTL1_NMINTE
) {
DBG
("enabled, but NMINTE won't be set, ACBCTL1=0x%02x\n", val
);
return -ENXIO
;
}
return 0;
}
static int __init scx200_acb_create
(int base
, int index
)
{
struct scx200_acb_iface
*iface
;
struct i2c_adapter
*adapter
;
int rc
= 0;
char description
[64];
iface
= kmalloc
(sizeof(*iface
), GFP_KERNEL
);
if (!iface
) {
printk
(KERN_ERR NAME
": can't allocate memory\n");
rc
= -ENOMEM
;
goto errout
;
}
memset(iface
, 0, sizeof(*iface
));
adapter
= &iface
->adapter
;
i2c_set_adapdata
(adapter
, iface
);
snprintf(adapter
->name
, I2C_NAME_SIZE
, "SCx200 ACB%d", index
);
adapter
->owner
= THIS_MODULE
;
adapter
->id
= I2C_ALGO_SMBUS
;
adapter
->algo
= &scx200_acb_algorithm
;
init_MUTEX
(&iface
->sem
);
snprintf(description
, sizeof(description
), "NatSemi SCx200 ACCESS.bus [%s]", adapter
->name
);
if (request_region
(base
, 8, description
) == 0) {
dev_err
(&adapter
->dev
, "can't allocate io 0x%x-0x%x\n",
base
, base
+ 8-1);
rc
= -EBUSY
;
goto errout
;
}
iface
->base
= base
;
rc
= scx200_acb_probe
(iface
);
if (rc
) {
dev_warn
(&adapter
->dev
, "probe failed\n");
goto errout
;
}
scx200_acb_reset
(iface
);
if (i2c_add_adapter
(adapter
) < 0) {
dev_err
(&adapter
->dev
, "failed to register\n");
rc
= -ENODEV
;
goto errout
;
}
lock_kernel
();
iface
->next
= scx200_acb_list
;
scx200_acb_list
= iface
;
unlock_kernel
();
return 0;
errout
:
if (iface
) {
if (iface
->base
)
release_region
(iface
->base
, 8);
kfree
(iface
);
}
return rc
;
}
static int __init scx200_acb_init
(void)
{
int i
;
int rc
;
printk
(KERN_DEBUG NAME
": NatSemi SCx200 ACCESS.bus Driver\n");
/* Verify that this really is a SCx200 processor */
if (pci_find_device
(PCI_VENDOR_ID_NS
,
PCI_DEVICE_ID_NS_SCx200_BRIDGE
,
NULL
) == NULL
)
return -ENODEV
;
rc
= -ENXIO
;
for (i
= 0; i
< MAX_DEVICES
; ++i
) {
if (base
[i
] > 0)
rc
= scx200_acb_create
(base
[i
], i
);
}
if (scx200_acb_list
)
return 0;
return rc
;
}
static void __exit scx200_acb_cleanup
(void)
{
struct scx200_acb_iface
*iface
;
lock_kernel
();
while ((iface
= scx200_acb_list
) != NULL
) {
scx200_acb_list
= iface
->next
;
unlock_kernel
();
i2c_del_adapter
(&iface
->adapter
);
release_region
(iface
->base
, 8);
kfree
(iface
);
lock_kernel
();
}
unlock_kernel
();
}
module_init
(scx200_acb_init
);
module_exit
(scx200_acb_cleanup
);
/*
Local variables:
compile-command: "make -k -C ../.. SUBDIRS=drivers/i2c modules"
c-basic-offset: 8
End:
*/