M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
#
# CAIF physical drivers
#
comment "CAIF transport drivers"
config CAIF_TTY
tristate "CAIF TTY transport driver"
depends on CAIF
default n
---help---
The CAIF TTY transport driver is a Line Discipline (ldisc)
identified as N_CAIF. When this ldisc is opened from user space
it will redirect the TTY's traffic into the CAIF stack.
config CAIF_SPI_SLAVE
tristate "CAIF SPI transport driver for slave interface"
depends on CAIF && HAS_DMA
default n
---help---
The CAIF Link layer SPI Protocol driver for Slave SPI interface.
This driver implements a platform driver to accommodate for a
platform specific SPI device. A sample CAIF SPI Platform device is
provided in Documentation/networking/caif/spi_porting.txt
config CAIF_SPI_SYNC
bool "Next command and length in start of frame"
depends on CAIF_SPI_SLAVE
default n
---help---
Putting the next command and length in the start of the frame can
help to synchronize to the next transfer in case of over or under-runs.
This option also needs to be enabled on the modem.
config CAIF_SHM
tristate "CAIF shared memory protocol driver"
depends on CAIF && U5500_MBOX
default n
---help---
The CAIF shared memory protocol driver for the STE UX5500 platform.
config CAIF_HSI
tristate "CAIF HSI transport driver"
depends on CAIF
default n
---help---
The caif low level driver for CAIF over HSI.
Be aware that if you enable this then you also need to
enable a low-level HSI driver.
+15
View File
@@ -0,0 +1,15 @@
ccflags-$(CONFIG_CAIF_DEBUG) := -DDEBUG
# Serial interface
obj-$(CONFIG_CAIF_TTY) += caif_serial.o
# SPI slave physical interfaces module
cfspi_slave-objs := caif_spi.o caif_spi_slave.o
obj-$(CONFIG_CAIF_SPI_SLAVE) += cfspi_slave.o
# Shared memory
caif_shm-objs := caif_shmcore.o caif_shm_u5500.o
obj-$(CONFIG_CAIF_SHM) += caif_shm.o
# HSI interface
obj-$(CONFIG_CAIF_HSI) += caif_hsi.o
File diff suppressed because it is too large Load Diff
+455
View File
@@ -0,0 +1,455 @@
/*
* Copyright (C) ST-Ericsson AB 2010
* Author: Sjur Brendeland / sjur.brandeland@stericsson.com
* License terms: GNU General Public License (GPL) version 2
*/
#include <linux/hardirq.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/tty.h>
#include <linux/file.h>
#include <linux/if_arp.h>
#include <net/caif/caif_device.h>
#include <net/caif/cfcnfg.h>
#include <linux/err.h>
#include <linux/debugfs.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjur Brendeland<sjur.brandeland@stericsson.com>");
MODULE_DESCRIPTION("CAIF serial device TTY line discipline");
MODULE_LICENSE("GPL");
MODULE_ALIAS_LDISC(N_CAIF);
#define SEND_QUEUE_LOW 10
#define SEND_QUEUE_HIGH 100
#define CAIF_SENDING 1 /* Bit 1 = 0x02*/
#define CAIF_FLOW_OFF_SENT 4 /* Bit 4 = 0x10 */
#define MAX_WRITE_CHUNK 4096
#define ON 1
#define OFF 0
#define CAIF_MAX_MTU 4096
/*This list is protected by the rtnl lock. */
static LIST_HEAD(ser_list);
static bool ser_loop;
module_param(ser_loop, bool, S_IRUGO);
MODULE_PARM_DESC(ser_loop, "Run in simulated loopback mode.");
static bool ser_use_stx = true;
module_param(ser_use_stx, bool, S_IRUGO);
MODULE_PARM_DESC(ser_use_stx, "STX enabled or not.");
static bool ser_use_fcs = true;
module_param(ser_use_fcs, bool, S_IRUGO);
MODULE_PARM_DESC(ser_use_fcs, "FCS enabled or not.");
static int ser_write_chunk = MAX_WRITE_CHUNK;
module_param(ser_write_chunk, int, S_IRUGO);
MODULE_PARM_DESC(ser_write_chunk, "Maximum size of data written to UART.");
static struct dentry *debugfsdir;
static int caif_net_open(struct net_device *dev);
static int caif_net_close(struct net_device *dev);
struct ser_device {
struct caif_dev_common common;
struct list_head node;
struct net_device *dev;
struct sk_buff_head head;
struct tty_struct *tty;
bool tx_started;
unsigned long state;
char *tty_name;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_tty_dir;
struct debugfs_blob_wrapper tx_blob;
struct debugfs_blob_wrapper rx_blob;
u8 rx_data[128];
u8 tx_data[128];
u8 tty_status;
#endif
};
static void caifdev_setup(struct net_device *dev);
static void ldisc_tx_wakeup(struct tty_struct *tty);
#ifdef CONFIG_DEBUG_FS
static inline void update_tty_status(struct ser_device *ser)
{
ser->tty_status =
ser->tty->stopped << 5 |
ser->tty->hw_stopped << 4 |
ser->tty->flow_stopped << 3 |
ser->tty->packet << 2 |
ser->tty->low_latency << 1 |
ser->tty->warned;
}
static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty)
{
ser->debugfs_tty_dir =
debugfs_create_dir(tty->name, debugfsdir);
if (!IS_ERR(ser->debugfs_tty_dir)) {
debugfs_create_blob("last_tx_msg", S_IRUSR,
ser->debugfs_tty_dir,
&ser->tx_blob);
debugfs_create_blob("last_rx_msg", S_IRUSR,
ser->debugfs_tty_dir,
&ser->rx_blob);
debugfs_create_x32("ser_state", S_IRUSR,
ser->debugfs_tty_dir,
(u32 *)&ser->state);
debugfs_create_x8("tty_status", S_IRUSR,
ser->debugfs_tty_dir,
&ser->tty_status);
}
ser->tx_blob.data = ser->tx_data;
ser->tx_blob.size = 0;
ser->rx_blob.data = ser->rx_data;
ser->rx_blob.size = 0;
}
static inline void debugfs_deinit(struct ser_device *ser)
{
debugfs_remove_recursive(ser->debugfs_tty_dir);
}
static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size)
{
if (size > sizeof(ser->rx_data))
size = sizeof(ser->rx_data);
memcpy(ser->rx_data, data, size);
ser->rx_blob.data = ser->rx_data;
ser->rx_blob.size = size;
}
static inline void debugfs_tx(struct ser_device *ser, const u8 *data, int size)
{
if (size > sizeof(ser->tx_data))
size = sizeof(ser->tx_data);
memcpy(ser->tx_data, data, size);
ser->tx_blob.data = ser->tx_data;
ser->tx_blob.size = size;
}
#else
static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty)
{
}
static inline void debugfs_deinit(struct ser_device *ser)
{
}
static inline void update_tty_status(struct ser_device *ser)
{
}
static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size)
{
}
static inline void debugfs_tx(struct ser_device *ser, const u8 *data, int size)
{
}
#endif
static void ldisc_receive(struct tty_struct *tty, const u8 *data,
char *flags, int count)
{
struct sk_buff *skb = NULL;
struct ser_device *ser;
int ret;
u8 *p;
ser = tty->disc_data;
/*
* NOTE: flags may contain information about break or overrun.
* This is not yet handled.
*/
/*
* Workaround for garbage at start of transmission,
* only enable if STX handling is not enabled.
*/
if (!ser->common.use_stx && !ser->tx_started) {
dev_info(&ser->dev->dev,
"Bytes received before initial transmission -"
"bytes discarded.\n");
return;
}
BUG_ON(ser->dev == NULL);
/* Get a suitable caif packet and copy in data. */
skb = netdev_alloc_skb(ser->dev, count+1);
if (skb == NULL)
return;
p = skb_put(skb, count);
memcpy(p, data, count);
skb->protocol = htons(ETH_P_CAIF);
skb_reset_mac_header(skb);
skb->dev = ser->dev;
debugfs_rx(ser, data, count);
/* Push received packet up the stack. */
ret = netif_rx_ni(skb);
if (!ret) {
ser->dev->stats.rx_packets++;
ser->dev->stats.rx_bytes += count;
} else
++ser->dev->stats.rx_dropped;
update_tty_status(ser);
}
static int handle_tx(struct ser_device *ser)
{
struct tty_struct *tty;
struct sk_buff *skb;
int tty_wr, len, room;
tty = ser->tty;
ser->tx_started = true;
/* Enter critical section */
if (test_and_set_bit(CAIF_SENDING, &ser->state))
return 0;
/* skb_peek is safe because handle_tx is called after skb_queue_tail */
while ((skb = skb_peek(&ser->head)) != NULL) {
/* Make sure you don't write too much */
len = skb->len;
room = tty_write_room(tty);
if (!room)
break;
if (room > ser_write_chunk)
room = ser_write_chunk;
if (len > room)
len = room;
/* Write to tty or loopback */
if (!ser_loop) {
tty_wr = tty->ops->write(tty, skb->data, len);
update_tty_status(ser);
} else {
tty_wr = len;
ldisc_receive(tty, skb->data, NULL, len);
}
ser->dev->stats.tx_packets++;
ser->dev->stats.tx_bytes += tty_wr;
/* Error on TTY ?! */
if (tty_wr < 0)
goto error;
/* Reduce buffer written, and discard if empty */
skb_pull(skb, tty_wr);
if (skb->len == 0) {
struct sk_buff *tmp = skb_dequeue(&ser->head);
WARN_ON(tmp != skb);
if (in_interrupt())
dev_kfree_skb_irq(skb);
else
kfree_skb(skb);
}
}
/* Send flow off if queue is empty */
if (ser->head.qlen <= SEND_QUEUE_LOW &&
test_and_clear_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
ser->common.flowctrl != NULL)
ser->common.flowctrl(ser->dev, ON);
clear_bit(CAIF_SENDING, &ser->state);
return 0;
error:
clear_bit(CAIF_SENDING, &ser->state);
return tty_wr;
}
static int caif_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ser_device *ser;
BUG_ON(dev == NULL);
ser = netdev_priv(dev);
/* Send flow off once, on high water mark */
if (ser->head.qlen > SEND_QUEUE_HIGH &&
!test_and_set_bit(CAIF_FLOW_OFF_SENT, &ser->state) &&
ser->common.flowctrl != NULL)
ser->common.flowctrl(ser->dev, OFF);
skb_queue_tail(&ser->head, skb);
return handle_tx(ser);
}
static void ldisc_tx_wakeup(struct tty_struct *tty)
{
struct ser_device *ser;
ser = tty->disc_data;
BUG_ON(ser == NULL);
WARN_ON(ser->tty != tty);
handle_tx(ser);
}
static int ldisc_open(struct tty_struct *tty)
{
struct ser_device *ser;
struct net_device *dev;
char name[64];
int result;
/* No write no play */
if (tty->ops->write == NULL)
return -EOPNOTSUPP;
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG))
return -EPERM;
sprintf(name, "cf%s", tty->name);
dev = alloc_netdev(sizeof(*ser), name, caifdev_setup);
ser = netdev_priv(dev);
ser->tty = tty_kref_get(tty);
ser->dev = dev;
debugfs_init(ser, tty);
tty->receive_room = N_TTY_BUF_SIZE;
tty->disc_data = ser;
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
rtnl_lock();
result = register_netdevice(dev);
if (result) {
rtnl_unlock();
free_netdev(dev);
return -ENODEV;
}
list_add(&ser->node, &ser_list);
rtnl_unlock();
netif_stop_queue(dev);
update_tty_status(ser);
return 0;
}
static void ldisc_close(struct tty_struct *tty)
{
struct ser_device *ser = tty->disc_data;
/* Remove may be called inside or outside of rtnl_lock */
int islocked = rtnl_is_locked();
if (!islocked)
rtnl_lock();
/* device is freed automagically by net-sysfs */
dev_close(ser->dev);
unregister_netdevice(ser->dev);
list_del(&ser->node);
debugfs_deinit(ser);
tty_kref_put(ser->tty);
if (!islocked)
rtnl_unlock();
}
/* The line discipline structure. */
static struct tty_ldisc_ops caif_ldisc = {
.owner = THIS_MODULE,
.magic = TTY_LDISC_MAGIC,
.name = "n_caif",
.open = ldisc_open,
.close = ldisc_close,
.receive_buf = ldisc_receive,
.write_wakeup = ldisc_tx_wakeup
};
static int register_ldisc(void)
{
int result;
result = tty_register_ldisc(N_CAIF, &caif_ldisc);
if (result < 0) {
pr_err("cannot register CAIF ldisc=%d err=%d\n", N_CAIF,
result);
return result;
}
return result;
}
static const struct net_device_ops netdev_ops = {
.ndo_open = caif_net_open,
.ndo_stop = caif_net_close,
.ndo_start_xmit = caif_xmit
};
static void caifdev_setup(struct net_device *dev)
{
struct ser_device *serdev = netdev_priv(dev);
dev->features = 0;
dev->netdev_ops = &netdev_ops;
dev->type = ARPHRD_CAIF;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = CAIF_MAX_MTU;
dev->tx_queue_len = 0;
dev->destructor = free_netdev;
skb_queue_head_init(&serdev->head);
serdev->common.link_select = CAIF_LINK_LOW_LATENCY;
serdev->common.use_frag = true;
serdev->common.use_stx = ser_use_stx;
serdev->common.use_fcs = ser_use_fcs;
serdev->dev = dev;
}
static int caif_net_open(struct net_device *dev)
{
netif_wake_queue(dev);
return 0;
}
static int caif_net_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static int __init caif_ser_init(void)
{
int ret;
ret = register_ldisc();
debugfsdir = debugfs_create_dir("caif_serial", NULL);
return ret;
}
static void __exit caif_ser_exit(void)
{
struct ser_device *ser = NULL;
struct list_head *node;
struct list_head *_tmp;
list_for_each_safe(node, _tmp, &ser_list) {
ser = list_entry(node, struct ser_device, node);
dev_close(ser->dev);
unregister_netdevice(ser->dev);
list_del(node);
}
tty_unregister_ldisc(N_CAIF);
debugfs_remove_recursive(debugfsdir);
}
module_init(caif_ser_init);
module_exit(caif_ser_exit);
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright (C) ST-Ericsson AB 2010
* Contact: Sjur Brendeland / sjur.brandeland@stericsson.com
* Author: Amarnath Revanna / amarnath.bangalore.revanna@stericsson.com
* License terms: GNU General Public License (GPL) version 2
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":" fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <mach/mbox-db5500.h>
#include <net/caif/caif_shm.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CAIF Shared Memory protocol driver");
#define MAX_SHM_INSTANCES 1
enum {
MBX_ACC0,
MBX_ACC1,
MBX_DSP
};
static struct shmdev_layer shmdev_lyr[MAX_SHM_INSTANCES];
static unsigned int shm_start;
static unsigned int shm_size;
module_param(shm_size, uint , 0440);
MODULE_PARM_DESC(shm_total_size, "Start of SHM shared memory");
module_param(shm_start, uint , 0440);
MODULE_PARM_DESC(shm_total_start, "Total Size of SHM shared memory");
static int shmdev_send_msg(u32 dev_id, u32 mbx_msg)
{
/* Always block until msg is written successfully */
mbox_send(shmdev_lyr[dev_id].hmbx, mbx_msg, true);
return 0;
}
static int shmdev_mbx_setup(void *pshmdrv_cb, struct shmdev_layer *pshm_dev,
void *pshm_drv)
{
/*
* For UX5500, we have only 1 SHM instance which uses MBX0
* for communication with the peer modem
*/
pshm_dev->hmbx = mbox_setup(MBX_ACC0, pshmdrv_cb, pshm_drv);
if (!pshm_dev->hmbx)
return -ENODEV;
else
return 0;
}
static int __init caif_shmdev_init(void)
{
int i, result;
/* Loop is currently overkill, there is only one instance */
for (i = 0; i < MAX_SHM_INSTANCES; i++) {
shmdev_lyr[i].shm_base_addr = shm_start;
shmdev_lyr[i].shm_total_sz = shm_size;
if (((char *)shmdev_lyr[i].shm_base_addr == NULL)
|| (shmdev_lyr[i].shm_total_sz <= 0)) {
pr_warn("ERROR,"
"Shared memory Address and/or Size incorrect"
", Bailing out ...\n");
result = -EINVAL;
goto clean;
}
pr_info("SHM AREA (instance %d) STARTS"
" AT %p\n", i, (char *)shmdev_lyr[i].shm_base_addr);
shmdev_lyr[i].shm_id = i;
shmdev_lyr[i].pshmdev_mbxsend = shmdev_send_msg;
shmdev_lyr[i].pshmdev_mbxsetup = shmdev_mbx_setup;
/*
* Finally, CAIF core module is called with details in place:
* 1. SHM base address
* 2. SHM size
* 3. MBX handle
*/
result = caif_shmcore_probe(&shmdev_lyr[i]);
if (result) {
pr_warn("ERROR[%d],"
"Could not probe SHM core (instance %d)"
" Bailing out ...\n", result, i);
goto clean;
}
}
return 0;
clean:
/*
* For now, we assume that even if one instance of SHM fails, we bail
* out of the driver support completely. For this, we need to release
* any memory allocated and unregister any instance of SHM net device.
*/
for (i = 0; i < MAX_SHM_INSTANCES; i++) {
if (shmdev_lyr[i].pshm_netdev)
unregister_netdev(shmdev_lyr[i].pshm_netdev);
}
return result;
}
static void __exit caif_shmdev_exit(void)
{
int i;
for (i = 0; i < MAX_SHM_INSTANCES; i++) {
caif_shmcore_remove(shmdev_lyr[i].pshm_netdev);
kfree((void *)shmdev_lyr[i].shm_base_addr);
}
}
module_init(caif_shmdev_init);
module_exit(caif_shmdev_exit);
+749
View File
@@ -0,0 +1,749 @@
/*
* Copyright (C) ST-Ericsson AB 2010
* Contact: Sjur Brendeland / sjur.brandeland@stericsson.com
* Authors: Amarnath Revanna / amarnath.bangalore.revanna@stericsson.com,
* Daniel Martensson / daniel.martensson@stericsson.com
* License terms: GNU General Public License (GPL) version 2
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":" fmt
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <net/caif/caif_device.h>
#include <net/caif/caif_shm.h>
#define NR_TX_BUF 6
#define NR_RX_BUF 6
#define TX_BUF_SZ 0x2000
#define RX_BUF_SZ 0x2000
#define CAIF_NEEDED_HEADROOM 32
#define CAIF_FLOW_ON 1
#define CAIF_FLOW_OFF 0
#define LOW_WATERMARK 3
#define HIGH_WATERMARK 4
/* Maximum number of CAIF buffers per shared memory buffer. */
#define SHM_MAX_FRMS_PER_BUF 10
/*
* Size in bytes of the descriptor area
* (With end of descriptor signalling)
*/
#define SHM_CAIF_DESC_SIZE ((SHM_MAX_FRMS_PER_BUF + 1) * \
sizeof(struct shm_pck_desc))
/*
* Offset to the first CAIF frame within a shared memory buffer.
* Aligned on 32 bytes.
*/
#define SHM_CAIF_FRM_OFS (SHM_CAIF_DESC_SIZE + (SHM_CAIF_DESC_SIZE % 32))
/* Number of bytes for CAIF shared memory header. */
#define SHM_HDR_LEN 1
/* Number of padding bytes for the complete CAIF frame. */
#define SHM_FRM_PAD_LEN 4
#define CAIF_MAX_MTU 4096
#define SHM_SET_FULL(x) (((x+1) & 0x0F) << 0)
#define SHM_GET_FULL(x) (((x >> 0) & 0x0F) - 1)
#define SHM_SET_EMPTY(x) (((x+1) & 0x0F) << 4)
#define SHM_GET_EMPTY(x) (((x >> 4) & 0x0F) - 1)
#define SHM_FULL_MASK (0x0F << 0)
#define SHM_EMPTY_MASK (0x0F << 4)
struct shm_pck_desc {
/*
* Offset from start of shared memory area to start of
* shared memory CAIF frame.
*/
u32 frm_ofs;
u32 frm_len;
};
struct buf_list {
unsigned char *desc_vptr;
u32 phy_addr;
u32 index;
u32 len;
u32 frames;
u32 frm_ofs;
struct list_head list;
};
struct shm_caif_frm {
/* Number of bytes of padding before the CAIF frame. */
u8 hdr_ofs;
};
struct shmdrv_layer {
/* caif_dev_common must always be first in the structure*/
struct caif_dev_common cfdev;
u32 shm_tx_addr;
u32 shm_rx_addr;
u32 shm_base_addr;
u32 tx_empty_available;
spinlock_t lock;
struct list_head tx_empty_list;
struct list_head tx_pend_list;
struct list_head tx_full_list;
struct list_head rx_empty_list;
struct list_head rx_pend_list;
struct list_head rx_full_list;
struct workqueue_struct *pshm_tx_workqueue;
struct workqueue_struct *pshm_rx_workqueue;
struct work_struct shm_tx_work;
struct work_struct shm_rx_work;
struct sk_buff_head sk_qhead;
struct shmdev_layer *pshm_dev;
};
static int shm_netdev_open(struct net_device *shm_netdev)
{
netif_wake_queue(shm_netdev);
return 0;
}
static int shm_netdev_close(struct net_device *shm_netdev)
{
netif_stop_queue(shm_netdev);
return 0;
}
int caif_shmdrv_rx_cb(u32 mbx_msg, void *priv)
{
struct buf_list *pbuf;
struct shmdrv_layer *pshm_drv;
struct list_head *pos;
u32 avail_emptybuff = 0;
unsigned long flags = 0;
pshm_drv = priv;
/* Check for received buffers. */
if (mbx_msg & SHM_FULL_MASK) {
int idx;
spin_lock_irqsave(&pshm_drv->lock, flags);
/* Check whether we have any outstanding buffers. */
if (list_empty(&pshm_drv->rx_empty_list)) {
/* Release spin lock. */
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* We print even in IRQ context... */
pr_warn("No empty Rx buffers to fill: "
"mbx_msg:%x\n", mbx_msg);
/* Bail out. */
goto err_sync;
}
pbuf =
list_entry(pshm_drv->rx_empty_list.next,
struct buf_list, list);
idx = pbuf->index;
/* Check buffer synchronization. */
if (idx != SHM_GET_FULL(mbx_msg)) {
/* We print even in IRQ context... */
pr_warn(
"phyif_shm_mbx_msg_cb: RX full out of sync:"
" idx:%d, msg:%x SHM_GET_FULL(mbx_msg):%x\n",
idx, mbx_msg, SHM_GET_FULL(mbx_msg));
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* Bail out. */
goto err_sync;
}
list_del_init(&pbuf->list);
list_add_tail(&pbuf->list, &pshm_drv->rx_full_list);
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* Schedule RX work queue. */
if (!work_pending(&pshm_drv->shm_rx_work))
queue_work(pshm_drv->pshm_rx_workqueue,
&pshm_drv->shm_rx_work);
}
/* Check for emptied buffers. */
if (mbx_msg & SHM_EMPTY_MASK) {
int idx;
spin_lock_irqsave(&pshm_drv->lock, flags);
/* Check whether we have any outstanding buffers. */
if (list_empty(&pshm_drv->tx_full_list)) {
/* We print even in IRQ context... */
pr_warn("No TX to empty: msg:%x\n", mbx_msg);
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* Bail out. */
goto err_sync;
}
pbuf =
list_entry(pshm_drv->tx_full_list.next,
struct buf_list, list);
idx = pbuf->index;
/* Check buffer synchronization. */
if (idx != SHM_GET_EMPTY(mbx_msg)) {
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* We print even in IRQ context... */
pr_warn("TX empty "
"out of sync:idx:%d, msg:%x\n", idx, mbx_msg);
/* Bail out. */
goto err_sync;
}
list_del_init(&pbuf->list);
/* Reset buffer parameters. */
pbuf->frames = 0;
pbuf->frm_ofs = SHM_CAIF_FRM_OFS;
list_add_tail(&pbuf->list, &pshm_drv->tx_empty_list);
/* Check the available no. of buffers in the empty list */
list_for_each(pos, &pshm_drv->tx_empty_list)
avail_emptybuff++;
/* Check whether we have to wake up the transmitter. */
if ((avail_emptybuff > HIGH_WATERMARK) &&
(!pshm_drv->tx_empty_available)) {
pshm_drv->tx_empty_available = 1;
spin_unlock_irqrestore(&pshm_drv->lock, flags);
pshm_drv->cfdev.flowctrl
(pshm_drv->pshm_dev->pshm_netdev,
CAIF_FLOW_ON);
/* Schedule the work queue. if required */
if (!work_pending(&pshm_drv->shm_tx_work))
queue_work(pshm_drv->pshm_tx_workqueue,
&pshm_drv->shm_tx_work);
} else
spin_unlock_irqrestore(&pshm_drv->lock, flags);
}
return 0;
err_sync:
return -EIO;
}
static void shm_rx_work_func(struct work_struct *rx_work)
{
struct shmdrv_layer *pshm_drv;
struct buf_list *pbuf;
unsigned long flags = 0;
struct sk_buff *skb;
char *p;
int ret;
pshm_drv = container_of(rx_work, struct shmdrv_layer, shm_rx_work);
while (1) {
struct shm_pck_desc *pck_desc;
spin_lock_irqsave(&pshm_drv->lock, flags);
/* Check for received buffers. */
if (list_empty(&pshm_drv->rx_full_list)) {
spin_unlock_irqrestore(&pshm_drv->lock, flags);
break;
}
pbuf =
list_entry(pshm_drv->rx_full_list.next, struct buf_list,
list);
list_del_init(&pbuf->list);
spin_unlock_irqrestore(&pshm_drv->lock, flags);
/* Retrieve pointer to start of the packet descriptor area. */
pck_desc = (struct shm_pck_desc *) pbuf->desc_vptr;
/*
* Check whether descriptor contains a CAIF shared memory
* frame.
*/
while (pck_desc->frm_ofs) {
unsigned int frm_buf_ofs;
unsigned int frm_pck_ofs;
unsigned int frm_pck_len;
/*
* Check whether offset is within buffer limits
* (lower).
*/
if (pck_desc->frm_ofs <
(pbuf->phy_addr - pshm_drv->shm_base_addr))
break;
/*
* Check whether offset is within buffer limits
* (higher).
*/
if (pck_desc->frm_ofs >
((pbuf->phy_addr - pshm_drv->shm_base_addr) +
pbuf->len))
break;
/* Calculate offset from start of buffer. */
frm_buf_ofs =
pck_desc->frm_ofs - (pbuf->phy_addr -
pshm_drv->shm_base_addr);
/*
* Calculate offset and length of CAIF packet while
* taking care of the shared memory header.
*/
frm_pck_ofs =
frm_buf_ofs + SHM_HDR_LEN +
(*(pbuf->desc_vptr + frm_buf_ofs));
frm_pck_len =
(pck_desc->frm_len - SHM_HDR_LEN -
(*(pbuf->desc_vptr + frm_buf_ofs)));
/* Check whether CAIF packet is within buffer limits */
if ((frm_pck_ofs + pck_desc->frm_len) > pbuf->len)
break;
/* Get a suitable CAIF packet and copy in data. */
skb = netdev_alloc_skb(pshm_drv->pshm_dev->pshm_netdev,
frm_pck_len + 1);
if (skb == NULL) {
pr_info("OOM: Try next frame in descriptor\n");
break;
}
p = skb_put(skb, frm_pck_len);
memcpy(p, pbuf->desc_vptr + frm_pck_ofs, frm_pck_len);
skb->protocol = htons(ETH_P_CAIF);
skb_reset_mac_header(skb);
skb->dev = pshm_drv->pshm_dev->pshm_netdev;
/* Push received packet up the stack. */
ret = netif_rx_ni(skb);
if (!ret) {
pshm_drv->pshm_dev->pshm_netdev->stats.
rx_packets++;
pshm_drv->pshm_dev->pshm_netdev->stats.
rx_bytes += pck_desc->frm_len;
} else
++pshm_drv->pshm_dev->pshm_netdev->stats.
rx_dropped;
/* Move to next packet descriptor. */
pck_desc++;
}
spin_lock_irqsave(&pshm_drv->lock, flags);
list_add_tail(&pbuf->list, &pshm_drv->rx_pend_list);
spin_unlock_irqrestore(&pshm_drv->lock, flags);
}
/* Schedule the work queue. if required */
if (!work_pending(&pshm_drv->shm_tx_work))
queue_work(pshm_drv->pshm_tx_workqueue, &pshm_drv->shm_tx_work);
}
static void shm_tx_work_func(struct work_struct *tx_work)
{
u32 mbox_msg;
unsigned int frmlen, avail_emptybuff, append = 0;
unsigned long flags = 0;
struct buf_list *pbuf = NULL;
struct shmdrv_layer *pshm_drv;
struct shm_caif_frm *frm;
struct sk_buff *skb;
struct shm_pck_desc *pck_desc;
struct list_head *pos;
pshm_drv = container_of(tx_work, struct shmdrv_layer, shm_tx_work);
do {
/* Initialize mailbox message. */
mbox_msg = 0x00;
avail_emptybuff = 0;
spin_lock_irqsave(&pshm_drv->lock, flags);
/* Check for pending receive buffers. */
if (!list_empty(&pshm_drv->rx_pend_list)) {
pbuf = list_entry(pshm_drv->rx_pend_list.next,
struct buf_list, list);
list_del_init(&pbuf->list);
list_add_tail(&pbuf->list, &pshm_drv->rx_empty_list);
/*
* Value index is never changed,
* so read access should be safe.
*/
mbox_msg |= SHM_SET_EMPTY(pbuf->index);
}
skb = skb_peek(&pshm_drv->sk_qhead);
if (skb == NULL)
goto send_msg;
/* Check the available no. of buffers in the empty list */
list_for_each(pos, &pshm_drv->tx_empty_list)
avail_emptybuff++;
if ((avail_emptybuff < LOW_WATERMARK) &&
pshm_drv->tx_empty_available) {
/* Update blocking condition. */
pshm_drv->tx_empty_available = 0;
spin_unlock_irqrestore(&pshm_drv->lock, flags);
pshm_drv->cfdev.flowctrl
(pshm_drv->pshm_dev->pshm_netdev,
CAIF_FLOW_OFF);
spin_lock_irqsave(&pshm_drv->lock, flags);
}
/*
* We simply return back to the caller if we do not have space
* either in Tx pending list or Tx empty list. In this case,
* we hold the received skb in the skb list, waiting to
* be transmitted once Tx buffers become available
*/
if (list_empty(&pshm_drv->tx_empty_list))
goto send_msg;
/* Get the first free Tx buffer. */
pbuf = list_entry(pshm_drv->tx_empty_list.next,
struct buf_list, list);
do {
if (append) {
skb = skb_peek(&pshm_drv->sk_qhead);
if (skb == NULL)
break;
}
frm = (struct shm_caif_frm *)
(pbuf->desc_vptr + pbuf->frm_ofs);
frm->hdr_ofs = 0;
frmlen = 0;
frmlen += SHM_HDR_LEN + frm->hdr_ofs + skb->len;
/* Add tail padding if needed. */
if (frmlen % SHM_FRM_PAD_LEN)
frmlen += SHM_FRM_PAD_LEN -
(frmlen % SHM_FRM_PAD_LEN);
/*
* Verify that packet, header and additional padding
* can fit within the buffer frame area.
*/
if (frmlen >= (pbuf->len - pbuf->frm_ofs))
break;
if (!append) {
list_del_init(&pbuf->list);
append = 1;
}
skb = skb_dequeue(&pshm_drv->sk_qhead);
if (skb == NULL)
break;
/* Copy in CAIF frame. */
skb_copy_bits(skb, 0, pbuf->desc_vptr +
pbuf->frm_ofs + SHM_HDR_LEN +
frm->hdr_ofs, skb->len);
pshm_drv->pshm_dev->pshm_netdev->stats.tx_packets++;
pshm_drv->pshm_dev->pshm_netdev->stats.tx_bytes +=
frmlen;
dev_kfree_skb_irq(skb);
/* Fill in the shared memory packet descriptor area. */
pck_desc = (struct shm_pck_desc *) (pbuf->desc_vptr);
/* Forward to current frame. */
pck_desc += pbuf->frames;
pck_desc->frm_ofs = (pbuf->phy_addr -
pshm_drv->shm_base_addr) +
pbuf->frm_ofs;
pck_desc->frm_len = frmlen;
/* Terminate packet descriptor area. */
pck_desc++;
pck_desc->frm_ofs = 0;
/* Update buffer parameters. */
pbuf->frames++;
pbuf->frm_ofs += frmlen + (frmlen % 32);
} while (pbuf->frames < SHM_MAX_FRMS_PER_BUF);
/* Assign buffer as full. */
list_add_tail(&pbuf->list, &pshm_drv->tx_full_list);
append = 0;
mbox_msg |= SHM_SET_FULL(pbuf->index);
send_msg:
spin_unlock_irqrestore(&pshm_drv->lock, flags);
if (mbox_msg)
pshm_drv->pshm_dev->pshmdev_mbxsend
(pshm_drv->pshm_dev->shm_id, mbox_msg);
} while (mbox_msg);
}
static int shm_netdev_tx(struct sk_buff *skb, struct net_device *shm_netdev)
{
struct shmdrv_layer *pshm_drv;
pshm_drv = netdev_priv(shm_netdev);
skb_queue_tail(&pshm_drv->sk_qhead, skb);
/* Schedule Tx work queue. for deferred processing of skbs*/
if (!work_pending(&pshm_drv->shm_tx_work))
queue_work(pshm_drv->pshm_tx_workqueue, &pshm_drv->shm_tx_work);
return 0;
}
static const struct net_device_ops netdev_ops = {
.ndo_open = shm_netdev_open,
.ndo_stop = shm_netdev_close,
.ndo_start_xmit = shm_netdev_tx,
};
static void shm_netdev_setup(struct net_device *pshm_netdev)
{
struct shmdrv_layer *pshm_drv;
pshm_netdev->netdev_ops = &netdev_ops;
pshm_netdev->mtu = CAIF_MAX_MTU;
pshm_netdev->type = ARPHRD_CAIF;
pshm_netdev->hard_header_len = CAIF_NEEDED_HEADROOM;
pshm_netdev->tx_queue_len = 0;
pshm_netdev->destructor = free_netdev;
pshm_drv = netdev_priv(pshm_netdev);
/* Initialize structures in a clean state. */
memset(pshm_drv, 0, sizeof(struct shmdrv_layer));
pshm_drv->cfdev.link_select = CAIF_LINK_LOW_LATENCY;
}
int caif_shmcore_probe(struct shmdev_layer *pshm_dev)
{
int result, j;
struct shmdrv_layer *pshm_drv = NULL;
pshm_dev->pshm_netdev = alloc_netdev(sizeof(struct shmdrv_layer),
"cfshm%d", shm_netdev_setup);
if (!pshm_dev->pshm_netdev)
return -ENOMEM;
pshm_drv = netdev_priv(pshm_dev->pshm_netdev);
pshm_drv->pshm_dev = pshm_dev;
/*
* Initialization starts with the verification of the
* availability of MBX driver by calling its setup function.
* MBX driver must be available by this time for proper
* functioning of SHM driver.
*/
if ((pshm_dev->pshmdev_mbxsetup
(caif_shmdrv_rx_cb, pshm_dev, pshm_drv)) != 0) {
pr_warn("Could not config. SHM Mailbox,"
" Bailing out.....\n");
free_netdev(pshm_dev->pshm_netdev);
return -ENODEV;
}
skb_queue_head_init(&pshm_drv->sk_qhead);
pr_info("SHM DEVICE[%d] PROBED BY DRIVER, NEW SHM DRIVER"
" INSTANCE AT pshm_drv =0x%p\n",
pshm_drv->pshm_dev->shm_id, pshm_drv);
if (pshm_dev->shm_total_sz <
(NR_TX_BUF * TX_BUF_SZ + NR_RX_BUF * RX_BUF_SZ)) {
pr_warn("ERROR, Amount of available"
" Phys. SHM cannot accommodate current SHM "
"driver configuration, Bailing out ...\n");
free_netdev(pshm_dev->pshm_netdev);
return -ENOMEM;
}
pshm_drv->shm_base_addr = pshm_dev->shm_base_addr;
pshm_drv->shm_tx_addr = pshm_drv->shm_base_addr;
if (pshm_dev->shm_loopback)
pshm_drv->shm_rx_addr = pshm_drv->shm_tx_addr;
else
pshm_drv->shm_rx_addr = pshm_dev->shm_base_addr +
(NR_TX_BUF * TX_BUF_SZ);
spin_lock_init(&pshm_drv->lock);
INIT_LIST_HEAD(&pshm_drv->tx_empty_list);
INIT_LIST_HEAD(&pshm_drv->tx_pend_list);
INIT_LIST_HEAD(&pshm_drv->tx_full_list);
INIT_LIST_HEAD(&pshm_drv->rx_empty_list);
INIT_LIST_HEAD(&pshm_drv->rx_pend_list);
INIT_LIST_HEAD(&pshm_drv->rx_full_list);
INIT_WORK(&pshm_drv->shm_tx_work, shm_tx_work_func);
INIT_WORK(&pshm_drv->shm_rx_work, shm_rx_work_func);
pshm_drv->pshm_tx_workqueue =
create_singlethread_workqueue("shm_tx_work");
pshm_drv->pshm_rx_workqueue =
create_singlethread_workqueue("shm_rx_work");
for (j = 0; j < NR_TX_BUF; j++) {
struct buf_list *tx_buf =
kmalloc(sizeof(struct buf_list), GFP_KERNEL);
if (tx_buf == NULL) {
pr_warn("ERROR, Could not"
" allocate dynamic mem. for tx_buf,"
" Bailing out ...\n");
free_netdev(pshm_dev->pshm_netdev);
return -ENOMEM;
}
tx_buf->index = j;
tx_buf->phy_addr = pshm_drv->shm_tx_addr + (TX_BUF_SZ * j);
tx_buf->len = TX_BUF_SZ;
tx_buf->frames = 0;
tx_buf->frm_ofs = SHM_CAIF_FRM_OFS;
if (pshm_dev->shm_loopback)
tx_buf->desc_vptr = (unsigned char *)tx_buf->phy_addr;
else
tx_buf->desc_vptr =
ioremap(tx_buf->phy_addr, TX_BUF_SZ);
list_add_tail(&tx_buf->list, &pshm_drv->tx_empty_list);
}
for (j = 0; j < NR_RX_BUF; j++) {
struct buf_list *rx_buf =
kmalloc(sizeof(struct buf_list), GFP_KERNEL);
if (rx_buf == NULL) {
pr_warn("ERROR, Could not"
" allocate dynamic mem.for rx_buf,"
" Bailing out ...\n");
free_netdev(pshm_dev->pshm_netdev);
return -ENOMEM;
}
rx_buf->index = j;
rx_buf->phy_addr = pshm_drv->shm_rx_addr + (RX_BUF_SZ * j);
rx_buf->len = RX_BUF_SZ;
if (pshm_dev->shm_loopback)
rx_buf->desc_vptr = (unsigned char *)rx_buf->phy_addr;
else
rx_buf->desc_vptr =
ioremap(rx_buf->phy_addr, RX_BUF_SZ);
list_add_tail(&rx_buf->list, &pshm_drv->rx_empty_list);
}
pshm_drv->tx_empty_available = 1;
result = register_netdev(pshm_dev->pshm_netdev);
if (result)
pr_warn("ERROR[%d], SHM could not, "
"register with NW FRMWK Bailing out ...\n", result);
return result;
}
void caif_shmcore_remove(struct net_device *pshm_netdev)
{
struct buf_list *pbuf;
struct shmdrv_layer *pshm_drv = NULL;
pshm_drv = netdev_priv(pshm_netdev);
while (!(list_empty(&pshm_drv->tx_pend_list))) {
pbuf =
list_entry(pshm_drv->tx_pend_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
while (!(list_empty(&pshm_drv->tx_full_list))) {
pbuf =
list_entry(pshm_drv->tx_full_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
while (!(list_empty(&pshm_drv->tx_empty_list))) {
pbuf =
list_entry(pshm_drv->tx_empty_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
while (!(list_empty(&pshm_drv->rx_full_list))) {
pbuf =
list_entry(pshm_drv->tx_full_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
while (!(list_empty(&pshm_drv->rx_pend_list))) {
pbuf =
list_entry(pshm_drv->tx_pend_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
while (!(list_empty(&pshm_drv->rx_empty_list))) {
pbuf =
list_entry(pshm_drv->rx_empty_list.next,
struct buf_list, list);
list_del(&pbuf->list);
kfree(pbuf);
}
/* Destroy work queues. */
destroy_workqueue(pshm_drv->pshm_tx_workqueue);
destroy_workqueue(pshm_drv->pshm_rx_workqueue);
unregister_netdev(pshm_netdev);
}
+872
View File
@@ -0,0 +1,872 @@
/*
* Copyright (C) ST-Ericsson AB 2010
* Contact: Sjur Brendeland / sjur.brandeland@stericsson.com
* Author: Daniel Martensson / Daniel.Martensson@stericsson.com
* License terms: GNU General Public License (GPL) version 2.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/debugfs.h>
#include <linux/if_arp.h>
#include <net/caif/caif_layer.h>
#include <net/caif/caif_spi.h>
#ifndef CONFIG_CAIF_SPI_SYNC
#define FLAVOR "Flavour: Vanilla.\n"
#else
#define FLAVOR "Flavour: Master CMD&LEN at start.\n"
#endif /* CONFIG_CAIF_SPI_SYNC */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Daniel Martensson<daniel.martensson@stericsson.com>");
MODULE_DESCRIPTION("CAIF SPI driver");
/* Returns the number of padding bytes for alignment. */
#define PAD_POW2(x, pow) ((((x)&((pow)-1))==0) ? 0 : (((pow)-((x)&((pow)-1)))))
static bool spi_loop;
module_param(spi_loop, bool, S_IRUGO);
MODULE_PARM_DESC(spi_loop, "SPI running in loopback mode.");
/* SPI frame alignment. */
module_param(spi_frm_align, int, S_IRUGO);
MODULE_PARM_DESC(spi_frm_align, "SPI frame alignment.");
/*
* SPI padding options.
* Warning: must be a base of 2 (& operation used) and can not be zero !
*/
module_param(spi_up_head_align, int, S_IRUGO);
MODULE_PARM_DESC(spi_up_head_align, "SPI uplink head alignment.");
module_param(spi_up_tail_align, int, S_IRUGO);
MODULE_PARM_DESC(spi_up_tail_align, "SPI uplink tail alignment.");
module_param(spi_down_head_align, int, S_IRUGO);
MODULE_PARM_DESC(spi_down_head_align, "SPI downlink head alignment.");
module_param(spi_down_tail_align, int, S_IRUGO);
MODULE_PARM_DESC(spi_down_tail_align, "SPI downlink tail alignment.");
#ifdef CONFIG_ARM
#define BYTE_HEX_FMT "%02X"
#else
#define BYTE_HEX_FMT "%02hhX"
#endif
#define SPI_MAX_PAYLOAD_SIZE 4096
/*
* Threshold values for the SPI packet queue. Flowcontrol will be asserted
* when the number of packets exceeds HIGH_WATER_MARK. It will not be
* deasserted before the number of packets drops below LOW_WATER_MARK.
*/
#define LOW_WATER_MARK 100
#define HIGH_WATER_MARK (LOW_WATER_MARK*5)
#ifdef CONFIG_UML
/*
* We sometimes use UML for debugging, but it cannot handle
* dma_alloc_coherent so we have to wrap it.
*/
static inline void *dma_alloc(dma_addr_t *daddr)
{
return kmalloc(SPI_DMA_BUF_LEN, GFP_KERNEL);
}
static inline void dma_free(void *cpu_addr, dma_addr_t handle)
{
kfree(cpu_addr);
}
#else
static inline void *dma_alloc(dma_addr_t *daddr)
{
return dma_alloc_coherent(NULL, SPI_DMA_BUF_LEN, daddr,
GFP_KERNEL);
}
static inline void dma_free(void *cpu_addr, dma_addr_t handle)
{
dma_free_coherent(NULL, SPI_DMA_BUF_LEN, cpu_addr, handle);
}
#endif /* CONFIG_UML */
#ifdef CONFIG_DEBUG_FS
#define DEBUGFS_BUF_SIZE 4096
static struct dentry *dbgfs_root;
static inline void driver_debugfs_create(void)
{
dbgfs_root = debugfs_create_dir(cfspi_spi_driver.driver.name, NULL);
}
static inline void driver_debugfs_remove(void)
{
debugfs_remove(dbgfs_root);
}
static inline void dev_debugfs_rem(struct cfspi *cfspi)
{
debugfs_remove(cfspi->dbgfs_frame);
debugfs_remove(cfspi->dbgfs_state);
debugfs_remove(cfspi->dbgfs_dir);
}
static ssize_t dbgfs_state(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
char *buf;
int len = 0;
ssize_t size;
struct cfspi *cfspi = file->private_data;
buf = kzalloc(DEBUGFS_BUF_SIZE, GFP_KERNEL);
if (!buf)
return 0;
/* Print out debug information. */
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"CAIF SPI debug information:\n");
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len), FLAVOR);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"STATE: %d\n", cfspi->dbg_state);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Previous CMD: 0x%x\n", cfspi->pcmd);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Current CMD: 0x%x\n", cfspi->cmd);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Previous TX len: %d\n", cfspi->tx_ppck_len);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Previous RX len: %d\n", cfspi->rx_ppck_len);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Current TX len: %d\n", cfspi->tx_cpck_len);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Current RX len: %d\n", cfspi->rx_cpck_len);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Next TX len: %d\n", cfspi->tx_npck_len);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Next RX len: %d\n", cfspi->rx_npck_len);
if (len > DEBUGFS_BUF_SIZE)
len = DEBUGFS_BUF_SIZE;
size = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return size;
}
static ssize_t print_frame(char *buf, size_t size, char *frm,
size_t count, size_t cut)
{
int len = 0;
int i;
for (i = 0; i < count; i++) {
len += snprintf((buf + len), (size - len),
"[0x" BYTE_HEX_FMT "]",
frm[i]);
if ((i == cut) && (count > (cut * 2))) {
/* Fast forward. */
i = count - cut;
len += snprintf((buf + len), (size - len),
"--- %u bytes skipped ---\n",
(int)(count - (cut * 2)));
}
if ((!(i % 10)) && i) {
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"\n");
}
}
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len), "\n");
return len;
}
static ssize_t dbgfs_frame(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
char *buf;
int len = 0;
ssize_t size;
struct cfspi *cfspi;
cfspi = file->private_data;
buf = kzalloc(DEBUGFS_BUF_SIZE, GFP_KERNEL);
if (!buf)
return 0;
/* Print out debug information. */
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Current frame:\n");
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Tx data (Len: %d):\n", cfspi->tx_cpck_len);
len += print_frame((buf + len), (DEBUGFS_BUF_SIZE - len),
cfspi->xfer.va_tx[0],
(cfspi->tx_cpck_len + SPI_CMD_SZ), 100);
len += snprintf((buf + len), (DEBUGFS_BUF_SIZE - len),
"Rx data (Len: %d):\n", cfspi->rx_cpck_len);
len += print_frame((buf + len), (DEBUGFS_BUF_SIZE - len),
cfspi->xfer.va_rx,
(cfspi->rx_cpck_len + SPI_CMD_SZ), 100);
size = simple_read_from_buffer(user_buf, count, ppos, buf, len);
kfree(buf);
return size;
}
static const struct file_operations dbgfs_state_fops = {
.open = simple_open,
.read = dbgfs_state,
.owner = THIS_MODULE
};
static const struct file_operations dbgfs_frame_fops = {
.open = simple_open,
.read = dbgfs_frame,
.owner = THIS_MODULE
};
static inline void dev_debugfs_add(struct cfspi *cfspi)
{
cfspi->dbgfs_dir = debugfs_create_dir(cfspi->pdev->name, dbgfs_root);
cfspi->dbgfs_state = debugfs_create_file("state", S_IRUGO,
cfspi->dbgfs_dir, cfspi,
&dbgfs_state_fops);
cfspi->dbgfs_frame = debugfs_create_file("frame", S_IRUGO,
cfspi->dbgfs_dir, cfspi,
&dbgfs_frame_fops);
}
inline void cfspi_dbg_state(struct cfspi *cfspi, int state)
{
cfspi->dbg_state = state;
};
#else
static inline void driver_debugfs_create(void)
{
}
static inline void driver_debugfs_remove(void)
{
}
static inline void dev_debugfs_add(struct cfspi *cfspi)
{
}
static inline void dev_debugfs_rem(struct cfspi *cfspi)
{
}
inline void cfspi_dbg_state(struct cfspi *cfspi, int state)
{
}
#endif /* CONFIG_DEBUG_FS */
static LIST_HEAD(cfspi_list);
static spinlock_t cfspi_list_lock;
/* SPI uplink head alignment. */
static ssize_t show_up_head_align(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%d\n", spi_up_head_align);
}
static DRIVER_ATTR(up_head_align, S_IRUSR, show_up_head_align, NULL);
/* SPI uplink tail alignment. */
static ssize_t show_up_tail_align(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%d\n", spi_up_tail_align);
}
static DRIVER_ATTR(up_tail_align, S_IRUSR, show_up_tail_align, NULL);
/* SPI downlink head alignment. */
static ssize_t show_down_head_align(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%d\n", spi_down_head_align);
}
static DRIVER_ATTR(down_head_align, S_IRUSR, show_down_head_align, NULL);
/* SPI downlink tail alignment. */
static ssize_t show_down_tail_align(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%d\n", spi_down_tail_align);
}
static DRIVER_ATTR(down_tail_align, S_IRUSR, show_down_tail_align, NULL);
/* SPI frame alignment. */
static ssize_t show_frame_align(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%d\n", spi_frm_align);
}
static DRIVER_ATTR(frame_align, S_IRUSR, show_frame_align, NULL);
int cfspi_xmitfrm(struct cfspi *cfspi, u8 *buf, size_t len)
{
u8 *dst = buf;
caif_assert(buf);
if (cfspi->slave && !cfspi->slave_talked)
cfspi->slave_talked = true;
do {
struct sk_buff *skb;
struct caif_payload_info *info;
int spad = 0;
int epad;
skb = skb_dequeue(&cfspi->chead);
if (!skb)
break;
/*
* Calculate length of frame including SPI padding.
* The payload position is found in the control buffer.
*/
info = (struct caif_payload_info *)&skb->cb;
/*
* Compute head offset i.e. number of bytes to add to
* get the start of the payload aligned.
*/
if (spi_up_head_align > 1) {
spad = 1 + PAD_POW2((info->hdr_len + 1), spi_up_head_align);
*dst = (u8)(spad - 1);
dst += spad;
}
/* Copy in CAIF frame. */
skb_copy_bits(skb, 0, dst, skb->len);
dst += skb->len;
cfspi->ndev->stats.tx_packets++;
cfspi->ndev->stats.tx_bytes += skb->len;
/*
* Compute tail offset i.e. number of bytes to add to
* get the complete CAIF frame aligned.
*/
epad = PAD_POW2((skb->len + spad), spi_up_tail_align);
dst += epad;
dev_kfree_skb(skb);
} while ((dst - buf) < len);
return dst - buf;
}
int cfspi_xmitlen(struct cfspi *cfspi)
{
struct sk_buff *skb = NULL;
int frm_len = 0;
int pkts = 0;
/*
* Decommit previously committed frames.
* skb_queue_splice_tail(&cfspi->chead,&cfspi->qhead)
*/
while (skb_peek(&cfspi->chead)) {
skb = skb_dequeue_tail(&cfspi->chead);
skb_queue_head(&cfspi->qhead, skb);
}
do {
struct caif_payload_info *info = NULL;
int spad = 0;
int epad = 0;
skb = skb_dequeue(&cfspi->qhead);
if (!skb)
break;
/*
* Calculate length of frame including SPI padding.
* The payload position is found in the control buffer.
*/
info = (struct caif_payload_info *)&skb->cb;
/*
* Compute head offset i.e. number of bytes to add to
* get the start of the payload aligned.
*/
if (spi_up_head_align > 1)
spad = 1 + PAD_POW2((info->hdr_len + 1), spi_up_head_align);
/*
* Compute tail offset i.e. number of bytes to add to
* get the complete CAIF frame aligned.
*/
epad = PAD_POW2((skb->len + spad), spi_up_tail_align);
if ((skb->len + spad + epad + frm_len) <= CAIF_MAX_SPI_FRAME) {
skb_queue_tail(&cfspi->chead, skb);
pkts++;
frm_len += skb->len + spad + epad;
} else {
/* Put back packet. */
skb_queue_head(&cfspi->qhead, skb);
break;
}
} while (pkts <= CAIF_MAX_SPI_PKTS);
/*
* Send flow on if previously sent flow off
* and now go below the low water mark
*/
if (cfspi->flow_off_sent && cfspi->qhead.qlen < cfspi->qd_low_mark &&
cfspi->cfdev.flowctrl) {
cfspi->flow_off_sent = 0;
cfspi->cfdev.flowctrl(cfspi->ndev, 1);
}
return frm_len;
}
static void cfspi_ss_cb(bool assert, struct cfspi_ifc *ifc)
{
struct cfspi *cfspi = (struct cfspi *)ifc->priv;
/*
* The slave device is the master on the link. Interrupts before the
* slave has transmitted are considered spurious.
*/
if (cfspi->slave && !cfspi->slave_talked) {
printk(KERN_WARNING "CFSPI: Spurious SS interrupt.\n");
return;
}
if (!in_interrupt())
spin_lock(&cfspi->lock);
if (assert) {
set_bit(SPI_SS_ON, &cfspi->state);
set_bit(SPI_XFER, &cfspi->state);
} else {
set_bit(SPI_SS_OFF, &cfspi->state);
}
if (!in_interrupt())
spin_unlock(&cfspi->lock);
/* Wake up the xfer thread. */
if (assert)
wake_up_interruptible(&cfspi->wait);
}
static void cfspi_xfer_done_cb(struct cfspi_ifc *ifc)
{
struct cfspi *cfspi = (struct cfspi *)ifc->priv;
/* Transfer done, complete work queue */
complete(&cfspi->comp);
}
static int cfspi_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct cfspi *cfspi = NULL;
unsigned long flags;
if (!dev)
return -EINVAL;
cfspi = netdev_priv(dev);
skb_queue_tail(&cfspi->qhead, skb);
spin_lock_irqsave(&cfspi->lock, flags);
if (!test_and_set_bit(SPI_XFER, &cfspi->state)) {
/* Wake up xfer thread. */
wake_up_interruptible(&cfspi->wait);
}
spin_unlock_irqrestore(&cfspi->lock, flags);
/* Send flow off if number of bytes is above high water mark */
if (!cfspi->flow_off_sent &&
cfspi->qhead.qlen > cfspi->qd_high_mark &&
cfspi->cfdev.flowctrl) {
cfspi->flow_off_sent = 1;
cfspi->cfdev.flowctrl(cfspi->ndev, 0);
}
return 0;
}
int cfspi_rxfrm(struct cfspi *cfspi, u8 *buf, size_t len)
{
u8 *src = buf;
caif_assert(buf != NULL);
do {
int res;
struct sk_buff *skb = NULL;
int spad = 0;
int epad = 0;
u8 *dst = NULL;
int pkt_len = 0;
/*
* Compute head offset i.e. number of bytes added to
* get the start of the payload aligned.
*/
if (spi_down_head_align > 1) {
spad = 1 + *src;
src += spad;
}
/* Read length of CAIF frame (little endian). */
pkt_len = *src;
pkt_len |= ((*(src+1)) << 8) & 0xFF00;
pkt_len += 2; /* Add FCS fields. */
/* Get a suitable caif packet and copy in data. */
skb = netdev_alloc_skb(cfspi->ndev, pkt_len + 1);
caif_assert(skb != NULL);
dst = skb_put(skb, pkt_len);
memcpy(dst, src, pkt_len);
src += pkt_len;
skb->protocol = htons(ETH_P_CAIF);
skb_reset_mac_header(skb);
skb->dev = cfspi->ndev;
/*
* Push received packet up the stack.
*/
if (!spi_loop)
res = netif_rx_ni(skb);
else
res = cfspi_xmit(skb, cfspi->ndev);
if (!res) {
cfspi->ndev->stats.rx_packets++;
cfspi->ndev->stats.rx_bytes += pkt_len;
} else
cfspi->ndev->stats.rx_dropped++;
/*
* Compute tail offset i.e. number of bytes added to
* get the complete CAIF frame aligned.
*/
epad = PAD_POW2((pkt_len + spad), spi_down_tail_align);
src += epad;
} while ((src - buf) < len);
return src - buf;
}
static int cfspi_open(struct net_device *dev)
{
netif_wake_queue(dev);
return 0;
}
static int cfspi_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static int cfspi_init(struct net_device *dev)
{
int res = 0;
struct cfspi *cfspi = netdev_priv(dev);
/* Set flow info. */
cfspi->flow_off_sent = 0;
cfspi->qd_low_mark = LOW_WATER_MARK;
cfspi->qd_high_mark = HIGH_WATER_MARK;
/* Set slave info. */
if (!strncmp(cfspi_spi_driver.driver.name, "cfspi_sspi", 10)) {
cfspi->slave = true;
cfspi->slave_talked = false;
} else {
cfspi->slave = false;
cfspi->slave_talked = false;
}
/* Allocate DMA buffers. */
cfspi->xfer.va_tx[0] = dma_alloc(&cfspi->xfer.pa_tx[0]);
if (!cfspi->xfer.va_tx[0]) {
res = -ENODEV;
goto err_dma_alloc_tx_0;
}
cfspi->xfer.va_rx = dma_alloc(&cfspi->xfer.pa_rx);
if (!cfspi->xfer.va_rx) {
res = -ENODEV;
goto err_dma_alloc_rx;
}
/* Initialize the work queue. */
INIT_WORK(&cfspi->work, cfspi_xfer);
/* Initialize spin locks. */
spin_lock_init(&cfspi->lock);
/* Initialize flow control state. */
cfspi->flow_stop = false;
/* Initialize wait queue. */
init_waitqueue_head(&cfspi->wait);
/* Create work thread. */
cfspi->wq = create_singlethread_workqueue(dev->name);
if (!cfspi->wq) {
printk(KERN_WARNING "CFSPI: failed to create work queue.\n");
res = -ENODEV;
goto err_create_wq;
}
/* Initialize work queue. */
init_completion(&cfspi->comp);
/* Create debugfs entries. */
dev_debugfs_add(cfspi);
/* Set up the ifc. */
cfspi->ifc.ss_cb = cfspi_ss_cb;
cfspi->ifc.xfer_done_cb = cfspi_xfer_done_cb;
cfspi->ifc.priv = cfspi;
/* Add CAIF SPI device to list. */
spin_lock(&cfspi_list_lock);
list_add_tail(&cfspi->list, &cfspi_list);
spin_unlock(&cfspi_list_lock);
/* Schedule the work queue. */
queue_work(cfspi->wq, &cfspi->work);
return 0;
err_create_wq:
dma_free(cfspi->xfer.va_rx, cfspi->xfer.pa_rx);
err_dma_alloc_rx:
dma_free(cfspi->xfer.va_tx[0], cfspi->xfer.pa_tx[0]);
err_dma_alloc_tx_0:
return res;
}
static void cfspi_uninit(struct net_device *dev)
{
struct cfspi *cfspi = netdev_priv(dev);
/* Remove from list. */
spin_lock(&cfspi_list_lock);
list_del(&cfspi->list);
spin_unlock(&cfspi_list_lock);
cfspi->ndev = NULL;
/* Free DMA buffers. */
dma_free(cfspi->xfer.va_rx, cfspi->xfer.pa_rx);
dma_free(cfspi->xfer.va_tx[0], cfspi->xfer.pa_tx[0]);
set_bit(SPI_TERMINATE, &cfspi->state);
wake_up_interruptible(&cfspi->wait);
destroy_workqueue(cfspi->wq);
/* Destroy debugfs directory and files. */
dev_debugfs_rem(cfspi);
return;
}
static const struct net_device_ops cfspi_ops = {
.ndo_open = cfspi_open,
.ndo_stop = cfspi_close,
.ndo_init = cfspi_init,
.ndo_uninit = cfspi_uninit,
.ndo_start_xmit = cfspi_xmit
};
static void cfspi_setup(struct net_device *dev)
{
struct cfspi *cfspi = netdev_priv(dev);
dev->features = 0;
dev->netdev_ops = &cfspi_ops;
dev->type = ARPHRD_CAIF;
dev->flags = IFF_NOARP | IFF_POINTOPOINT;
dev->tx_queue_len = 0;
dev->mtu = SPI_MAX_PAYLOAD_SIZE;
dev->destructor = free_netdev;
skb_queue_head_init(&cfspi->qhead);
skb_queue_head_init(&cfspi->chead);
cfspi->cfdev.link_select = CAIF_LINK_HIGH_BANDW;
cfspi->cfdev.use_frag = false;
cfspi->cfdev.use_stx = false;
cfspi->cfdev.use_fcs = false;
cfspi->ndev = dev;
}
int cfspi_spi_probe(struct platform_device *pdev)
{
struct cfspi *cfspi = NULL;
struct net_device *ndev;
struct cfspi_dev *dev;
int res;
dev = (struct cfspi_dev *)pdev->dev.platform_data;
ndev = alloc_netdev(sizeof(struct cfspi),
"cfspi%d", cfspi_setup);
if (!dev)
return -ENODEV;
cfspi = netdev_priv(ndev);
netif_stop_queue(ndev);
cfspi->ndev = ndev;
cfspi->pdev = pdev;
/* Assign the SPI device. */
cfspi->dev = dev;
/* Assign the device ifc to this SPI interface. */
dev->ifc = &cfspi->ifc;
/* Register network device. */
res = register_netdev(ndev);
if (res) {
printk(KERN_ERR "CFSPI: Reg. error: %d.\n", res);
goto err_net_reg;
}
return res;
err_net_reg:
free_netdev(ndev);
return res;
}
int cfspi_spi_remove(struct platform_device *pdev)
{
/* Everything is done in cfspi_uninit(). */
return 0;
}
static void __exit cfspi_exit_module(void)
{
struct list_head *list_node;
struct list_head *n;
struct cfspi *cfspi = NULL;
list_for_each_safe(list_node, n, &cfspi_list) {
cfspi = list_entry(list_node, struct cfspi, list);
unregister_netdev(cfspi->ndev);
}
/* Destroy sysfs files. */
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_up_head_align);
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_up_tail_align);
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_down_head_align);
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_down_tail_align);
driver_remove_file(&cfspi_spi_driver.driver, &driver_attr_frame_align);
/* Unregister platform driver. */
platform_driver_unregister(&cfspi_spi_driver);
/* Destroy debugfs root directory. */
driver_debugfs_remove();
}
static int __init cfspi_init_module(void)
{
int result;
/* Initialize spin lock. */
spin_lock_init(&cfspi_list_lock);
/* Register platform driver. */
result = platform_driver_register(&cfspi_spi_driver);
if (result) {
printk(KERN_ERR "Could not register platform SPI driver.\n");
goto err_dev_register;
}
/* Create sysfs files. */
result =
driver_create_file(&cfspi_spi_driver.driver,
&driver_attr_up_head_align);
if (result) {
printk(KERN_ERR "Sysfs creation failed 1.\n");
goto err_create_up_head_align;
}
result =
driver_create_file(&cfspi_spi_driver.driver,
&driver_attr_up_tail_align);
if (result) {
printk(KERN_ERR "Sysfs creation failed 2.\n");
goto err_create_up_tail_align;
}
result =
driver_create_file(&cfspi_spi_driver.driver,
&driver_attr_down_head_align);
if (result) {
printk(KERN_ERR "Sysfs creation failed 3.\n");
goto err_create_down_head_align;
}
result =
driver_create_file(&cfspi_spi_driver.driver,
&driver_attr_down_tail_align);
if (result) {
printk(KERN_ERR "Sysfs creation failed 4.\n");
goto err_create_down_tail_align;
}
result =
driver_create_file(&cfspi_spi_driver.driver,
&driver_attr_frame_align);
if (result) {
printk(KERN_ERR "Sysfs creation failed 5.\n");
goto err_create_frame_align;
}
driver_debugfs_create();
return result;
err_create_frame_align:
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_down_tail_align);
err_create_down_tail_align:
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_down_head_align);
err_create_down_head_align:
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_up_tail_align);
err_create_up_tail_align:
driver_remove_file(&cfspi_spi_driver.driver,
&driver_attr_up_head_align);
err_create_up_head_align:
err_dev_register:
return result;
}
module_init(cfspi_init_module);
module_exit(cfspi_exit_module);
+256
View File
@@ -0,0 +1,256 @@
/*
* Copyright (C) ST-Ericsson AB 2010
* Contact: Sjur Brendeland / sjur.brandeland@stericsson.com
* Author: Daniel Martensson / Daniel.Martensson@stericsson.com
* License terms: GNU General Public License (GPL) version 2.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/semaphore.h>
#include <linux/workqueue.h>
#include <linux/completion.h>
#include <linux/list.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/debugfs.h>
#include <net/caif/caif_spi.h>
#ifndef CONFIG_CAIF_SPI_SYNC
#define SPI_DATA_POS 0
static inline int forward_to_spi_cmd(struct cfspi *cfspi)
{
return cfspi->rx_cpck_len;
}
#else
#define SPI_DATA_POS SPI_CMD_SZ
static inline int forward_to_spi_cmd(struct cfspi *cfspi)
{
return 0;
}
#endif
int spi_frm_align = 2;
/*
* SPI padding options.
* Warning: must be a base of 2 (& operation used) and can not be zero !
*/
int spi_up_head_align = 1 << 1;
int spi_up_tail_align = 1 << 0;
int spi_down_head_align = 1 << 2;
int spi_down_tail_align = 1 << 1;
#ifdef CONFIG_DEBUG_FS
static inline void debugfs_store_prev(struct cfspi *cfspi)
{
/* Store previous command for debugging reasons.*/
cfspi->pcmd = cfspi->cmd;
/* Store previous transfer. */
cfspi->tx_ppck_len = cfspi->tx_cpck_len;
cfspi->rx_ppck_len = cfspi->rx_cpck_len;
}
#else
static inline void debugfs_store_prev(struct cfspi *cfspi)
{
}
#endif
void cfspi_xfer(struct work_struct *work)
{
struct cfspi *cfspi;
u8 *ptr = NULL;
unsigned long flags;
int ret;
cfspi = container_of(work, struct cfspi, work);
/* Initialize state. */
cfspi->cmd = SPI_CMD_EOT;
for (;;) {
cfspi_dbg_state(cfspi, CFSPI_STATE_WAITING);
/* Wait for master talk or transmit event. */
wait_event_interruptible(cfspi->wait,
test_bit(SPI_XFER, &cfspi->state) ||
test_bit(SPI_TERMINATE, &cfspi->state));
if (test_bit(SPI_TERMINATE, &cfspi->state))
return;
#if CFSPI_DBG_PREFILL
/* Prefill buffers for easier debugging. */
memset(cfspi->xfer.va_tx, 0xFF, SPI_DMA_BUF_LEN);
memset(cfspi->xfer.va_rx, 0xFF, SPI_DMA_BUF_LEN);
#endif /* CFSPI_DBG_PREFILL */
cfspi_dbg_state(cfspi, CFSPI_STATE_AWAKE);
/* Check whether we have a committed frame. */
if (cfspi->tx_cpck_len) {
int len;
cfspi_dbg_state(cfspi, CFSPI_STATE_FETCH_PKT);
/* Copy committed SPI frames after the SPI indication. */
ptr = (u8 *) cfspi->xfer.va_tx;
ptr += SPI_IND_SZ;
len = cfspi_xmitfrm(cfspi, ptr, cfspi->tx_cpck_len);
WARN_ON(len != cfspi->tx_cpck_len);
}
cfspi_dbg_state(cfspi, CFSPI_STATE_GET_NEXT);
/* Get length of next frame to commit. */
cfspi->tx_npck_len = cfspi_xmitlen(cfspi);
WARN_ON(cfspi->tx_npck_len > SPI_DMA_BUF_LEN);
/*
* Add indication and length at the beginning of the frame,
* using little endian.
*/
ptr = (u8 *) cfspi->xfer.va_tx;
*ptr++ = SPI_CMD_IND;
*ptr++ = (SPI_CMD_IND & 0xFF00) >> 8;
*ptr++ = cfspi->tx_npck_len & 0x00FF;
*ptr++ = (cfspi->tx_npck_len & 0xFF00) >> 8;
/* Calculate length of DMAs. */
cfspi->xfer.tx_dma_len = cfspi->tx_cpck_len + SPI_IND_SZ;
cfspi->xfer.rx_dma_len = cfspi->rx_cpck_len + SPI_CMD_SZ;
/* Add SPI TX frame alignment padding, if necessary. */
if (cfspi->tx_cpck_len &&
(cfspi->xfer.tx_dma_len % spi_frm_align)) {
cfspi->xfer.tx_dma_len += spi_frm_align -
(cfspi->xfer.tx_dma_len % spi_frm_align);
}
/* Add SPI RX frame alignment padding, if necessary. */
if (cfspi->rx_cpck_len &&
(cfspi->xfer.rx_dma_len % spi_frm_align)) {
cfspi->xfer.rx_dma_len += spi_frm_align -
(cfspi->xfer.rx_dma_len % spi_frm_align);
}
cfspi_dbg_state(cfspi, CFSPI_STATE_INIT_XFER);
/* Start transfer. */
ret = cfspi->dev->init_xfer(&cfspi->xfer, cfspi->dev);
WARN_ON(ret);
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_ACTIVE);
/*
* TODO: We might be able to make an assumption if this is the
* first loop. Make sure that minimum toggle time is respected.
*/
udelay(MIN_TRANSITION_TIME_USEC);
cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_ACTIVE);
/* Signal that we are ready to receive data. */
cfspi->dev->sig_xfer(true, cfspi->dev);
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_XFER_DONE);
/* Wait for transfer completion. */
wait_for_completion(&cfspi->comp);
cfspi_dbg_state(cfspi, CFSPI_STATE_XFER_DONE);
if (cfspi->cmd == SPI_CMD_EOT) {
/*
* Clear the master talk bit. A xfer is always at
* least two bursts.
*/
clear_bit(SPI_SS_ON, &cfspi->state);
}
cfspi_dbg_state(cfspi, CFSPI_STATE_WAIT_INACTIVE);
/* Make sure that the minimum toggle time is respected. */
if (SPI_XFER_TIME_USEC(cfspi->xfer.tx_dma_len,
cfspi->dev->clk_mhz) <
MIN_TRANSITION_TIME_USEC) {
udelay(MIN_TRANSITION_TIME_USEC -
SPI_XFER_TIME_USEC
(cfspi->xfer.tx_dma_len, cfspi->dev->clk_mhz));
}
cfspi_dbg_state(cfspi, CFSPI_STATE_SIG_INACTIVE);
/* De-assert transfer signal. */
cfspi->dev->sig_xfer(false, cfspi->dev);
/* Check whether we received a CAIF packet. */
if (cfspi->rx_cpck_len) {
int len;
cfspi_dbg_state(cfspi, CFSPI_STATE_DELIVER_PKT);
/* Parse SPI frame. */
ptr = ((u8 *)(cfspi->xfer.va_rx + SPI_DATA_POS));
len = cfspi_rxfrm(cfspi, ptr, cfspi->rx_cpck_len);
WARN_ON(len != cfspi->rx_cpck_len);
}
/* Check the next SPI command and length. */
ptr = (u8 *) cfspi->xfer.va_rx;
ptr += forward_to_spi_cmd(cfspi);
cfspi->cmd = *ptr++;
cfspi->cmd |= ((*ptr++) << 8) & 0xFF00;
cfspi->rx_npck_len = *ptr++;
cfspi->rx_npck_len |= ((*ptr++) << 8) & 0xFF00;
WARN_ON(cfspi->rx_npck_len > SPI_DMA_BUF_LEN);
WARN_ON(cfspi->cmd > SPI_CMD_EOT);
debugfs_store_prev(cfspi);
/* Check whether the master issued an EOT command. */
if (cfspi->cmd == SPI_CMD_EOT) {
/* Reset state. */
cfspi->tx_cpck_len = 0;
cfspi->rx_cpck_len = 0;
} else {
/* Update state. */
cfspi->tx_cpck_len = cfspi->tx_npck_len;
cfspi->rx_cpck_len = cfspi->rx_npck_len;
}
/*
* Check whether we need to clear the xfer bit.
* Spin lock needed for packet insertion.
* Test and clear of different bits
* are not supported.
*/
spin_lock_irqsave(&cfspi->lock, flags);
if (cfspi->cmd == SPI_CMD_EOT && !cfspi_xmitlen(cfspi)
&& !test_bit(SPI_SS_ON, &cfspi->state))
clear_bit(SPI_XFER, &cfspi->state);
spin_unlock_irqrestore(&cfspi->lock, flags);
}
}
struct platform_driver cfspi_spi_driver = {
.probe = cfspi_spi_probe,
.remove = cfspi_spi_remove,
.driver = {
.name = "cfspi_sspi",
.owner = THIS_MODULE,
},
};