/*
* Silicon Labs C2 port core Linux support
*
* Copyright (c) 2007 Rodolfo Giometti <giometti@linux.it>
* Copyright (c) 2007 Eurotech S.p.A. <info@eurotech.it>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/kmemcheck.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/c2port.h>
#define DRIVER_NAME "c2port"
#define DRIVER_VERSION "0.51.0"
static DEFINE_SPINLOCK(c2port_idr_lock);
static DEFINE_IDR(c2port_idr);
/*
* Local variables
*/
static struct class *c2port_class;
/*
* C2 registers & commands defines
*/
/* C2 registers */
#define C2PORT_DEVICEID 0x00
#define C2PORT_REVID 0x01
#define C2PORT_FPCTL 0x02
#define C2PORT_FPDAT 0xB4
/* C2 interface commands */
#define C2PORT_GET_VERSION 0x01
#define C2PORT_DEVICE_ERASE 0x03
#define C2PORT_BLOCK_READ 0x06
#define C2PORT_BLOCK_WRITE 0x07
#define C2PORT_PAGE_ERASE 0x08
/* C2 status return codes */
#define C2PORT_INVALID_COMMAND 0x00
#define C2PORT_COMMAND_FAILED 0x02
#define C2PORT_COMMAND_OK 0x0d
/*
* C2 port low level signal managements
*/
static void c2port_reset(struct c2port_device *dev)
{
struct c2port_ops *ops = dev->ops;
/* To reset the device we have to keep clock line low for at least
* 20us.
*/
local_irq_disable();
ops->c2ck_set(dev, 0);
udelay(25);
ops->c2ck_set(dev, 1);
local_irq_enable();
udelay(1);
}
static void c2port_strobe_ck(struct c2port_device *dev)
{
struct c2port_ops *ops = dev->ops;
/* During hi-low-hi transition we disable local IRQs to avoid
* interructions since C2 port specification says that it must be
* shorter than 5us, otherwise the microcontroller may consider
* it as a reset signal!
*/
local_irq_disable();
ops->c2ck_set(dev, 0);
udelay(1);
ops->c2ck_set(dev, 1);
local_irq_enable();
udelay(1);
}
/*
* C2 port basic functions
*/
static void c2port_write_ar(struct c2port_device *dev, u8 addr)
{
struct c2port_ops *ops = dev->ops;
int i;
/* START field */
c2port_strobe_ck(dev);
/* INS field (11b, LSB first) */
ops->c2d_dir(dev, 0);
ops->c2d_set(dev, 1);
c2port_strobe_ck(dev);
ops->c2d_set(dev, 1);
c2port_strobe_ck(dev);
/* ADDRESS field */
for (i = 0; i < 8; i++) {
ops->c2d_set(dev, addr & 0x01);
c2port_strobe_ck(dev);
addr >>= 1;
}
/* STOP field */
ops->c2d_dir(dev, 1);
c2port_strobe_ck(dev);
}
static int c2port_read_ar(struct c2port_device *dev, u8 *addr)
{
struct c2port_ops *ops = dev->ops;
int i;
/* START field */
c2port_strobe_ck(dev);
/* INS field (10b, LSB first) */
ops->c2d_dir(dev, 0);
ops->c2d_set(dev, 0);
c2port_strobe_ck(dev);
ops->c2d_set(dev, 1);
c2port_strobe_ck(dev);
/* ADDRESS field */
ops->c2d_dir(dev, 1);
*addr = 0;
for (i = 0; i < 8; i++) {
*addr >>= 1; /* shift in 8-bit ADDRESS field LSB first */
c2port_strobe_ck(dev);
if (ops->c2d_get(dev))
*addr |= 0x80;
}
/* STOP field */
c2port_strobe_ck(dev);
return 0;
}
static int c2port_write_dr(struct c2port_device *dev, u8 data)
{
struct c2port_ops *ops = dev->ops;
int timeout, i;
/* START field */
c2port_strobe_ck(dev);
/* INS field (01b, LSB first) */
ops->c2d_dir(dev, 0);
ops->c2d_set(dev, 1);
c2port_strobe_ck(dev);
ops->c2d_set(dev, 0);
c2port_strobe_ck(dev);
/* LENGTH field (00b, LSB first -> 1 byte) */
ops->c2d_set(dev, 0);
c2port_strobe_ck(dev);
ops->c2d_set(dev, 0);
c2port_strobe_ck(dev);
/* DATA field */
for (i = 0; i < 8; i++) {
ops->c2d_set(dev, data & 0x01);
c2port_strobe_ck(dev);
data >>= 1;
}
/* WAIT field */
ops->c2d_dir(dev, 1);
timeout = 20;
do {
c2port_strobe_ck(dev);
if (ops->c2d_get(dev))
break;