wcaxx: New driver for A4A/A4B/A8A/A8B analog cards.

This is a driver for the new line of analog cards that shares a common interface
with the TE133/TE134 and the TE435.

Signed-off-by: Shaun Ruffell <sruffell@digium.com>
This commit is contained in:
Shaun Ruffell 2013-07-01 11:09:10 -05:00
parent 26efcf6902
commit 348f6ab030
9 changed files with 6445 additions and 2 deletions

View File

@ -18,6 +18,14 @@ wcte13xp-objs := wcte13xp-base.o
CFLAGS_wcte13xp-base.o += -I$(src)/oct612x -I$(src)/oct612x/include -I$(src)/oct612x/octdeviceapi -I$(src)/oct612x/octdeviceapi/oct6100api
ifeq ($(HOTPLUG_FIRMWARE),yes)
CFLAGS_wcte13xp-base.o += -DHOTPLUG_FIRMWARE
obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCAXX) += wcaxx.o
wcaxx-objs := wcaxx-base.o wcxb_spi.o wcxb.o wcxb_flash.o
CFLAGS_wcaxx-base.o += -I$(src)/oct612x/ -I$(src)/oct612x/include -I$(src)/oct612x/octdeviceapi -I$(src)/oct612x/octdeviceapi/oct6100api
ifeq ($(HOTPLUG_FIRMWARE),yes)
CFLAGS_wcaxx-base.o += -DHOTPLUG_FIRMWARE
endif
endif
obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_WCTDM) += wctdm.o

View File

@ -3,7 +3,7 @@
#
# Makefile for firmware downloading/installation
#
# Copyright (C) 2007-2010, Digium, Inc.
# Copyright (C) 2007-2013, Digium, Inc.
#
# Joshua Colp <jcolp@digium.com>
#
@ -33,10 +33,16 @@ VPMOCT032_VERSION:=1.12.0
WCT820_VERSION:=1.76
TE133_VERSION:=6f0017
TE134_VERSION:=6f0017
A8A_VERSION:=1d0017
A8B_VERSION:=1d0017
A4A_VERSION:=a0017
A4B_VERSION:=a0017
FIRMWARE_URL:=http://downloads.digium.com/pub/telephony/firmware/releases
ALL_FIRMWARE=FIRMWARE-OCT6114-032 FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-OCT6114-256 FIRMWARE-TC400M FIRMWARE-HX8 FIRMWARE-VPMOCT032 FIRMWARE-TE820 FIRMWARE-TE133 FIRMWARE-TE134
ALL_FIRMWARE=FIRMWARE-OCT6114-032 FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-OCT6114-256
ALL_FIRMWARE+=FIRMWARE-TC400M FIRMWARE-HX8 FIRMWARE-VPMOCT032 FIRMWARE-TE820 FIRMWARE-TE133 FIRMWARE-TE134
ALL_FIRMWARE+=FIRMWARE-A8A FIRMWARE-A8B FIRMWARE-A4A FIRMWARE-A4B
# Firmware files should use the naming convention: dahdi-fw-<base name>-<sub name>-<version> or dahdi-fw-<base name>-<version>
# First example: dahdi-fw-oct6114-064-1.05.01
@ -55,6 +61,10 @@ FIRMWARE:=$(FIRMWARE:FIRMWARE-VPMOCT032=dahdi-fw-vpmoct032-$(VPMOCT032_VERSION).
FIRMWARE:=$(FIRMWARE:FIRMWARE-TE820=dahdi-fw-te820-$(WCT820_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-TE133=dahdi-fw-te133-$(TE133_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-TE134=dahdi-fw-te134-$(TE134_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-A8A=dahdi-fw-a8b-$(A8B_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-A8B=dahdi-fw-a8a-$(A8A_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-A4A=dahdi-fw-a4b-$(A4B_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-A4B=dahdi-fw-a4a-$(A4A_VERSION).tar.gz)
FWLOADERS:=dahdi-fwload-vpmadt032-$(VPMADT032_VERSION).tar.gz
@ -225,6 +235,50 @@ ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-te134-$(TE
else
@echo "Firmware dahdi-fw-te134.bin is already installed with required version $(TE134_VERSION)"
endif
ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8a-$(A8A_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-a8a-$(A8A_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes)
@echo "Installing dahdi-fw-a8a.bin to hotplug firmware directories"
@install -m 644 dahdi-fw-a8a.bin $(DESTDIR)/usr/lib/hotplug/firmware
@rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8a-*
@touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8a-$(A8A_VERSION)
@install -m 644 dahdi-fw-a8a.bin $(DESTDIR)/lib/firmware
@rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-a8a-*
@touch $(DESTDIR)/lib/firmware/.dahdi-fw-a8a-$(A8A_VERSION)
else
@echo "Firmware dahdi-fw-a8a.bin is already installed with required version $(A8A_VERSION)"
endif
ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8b-$(A8B_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-a8b-$(A8B_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes)
@echo "Installing dahdi-fw-a8b.bin to hotplug firmware directories"
@install -m 644 dahdi-fw-a8b.bin $(DESTDIR)/usr/lib/hotplug/firmware
@rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8b-*
@touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a8b-$(A8B_VERSION)
@install -m 644 dahdi-fw-a8b.bin $(DESTDIR)/lib/firmware
@rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-a8b-*
@touch $(DESTDIR)/lib/firmware/.dahdi-fw-a8b-$(A8B_VERSION)
else
@echo "Firmware dahdi-fw-a8b.bin is already installed with required version $(A8B_VERSION)"
endif
ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4a-$(A4A_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-a4a-$(A4A_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes)
@echo "Installing dahdi-fw-a4a.bin to hotplug firmware directories"
@install -m 644 dahdi-fw-a4a.bin $(DESTDIR)/usr/lib/hotplug/firmware
@rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4a-*
@touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4a-$(A4A_VERSION)
@install -m 644 dahdi-fw-a4a.bin $(DESTDIR)/lib/firmware
@rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-a4a-*
@touch $(DESTDIR)/lib/firmware/.dahdi-fw-a4a-$(A4A_VERSION)
else
@echo "Firmware dahdi-fw-a4a.bin is already installed with required version $(A4A_VERSION)"
endif
ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4b-$(A4B_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-a4b-$(A4B_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes)
@echo "Installing dahdi-fw-a4b.bin to hotplug firmware directories"
@install -m 644 dahdi-fw-a4b.bin $(DESTDIR)/usr/lib/hotplug/firmware
@rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4b-*
@touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-a4b-$(A4B_VERSION)
@install -m 645 dahdi-fw-a4b.bin $(DESTDIR)/lib/firmware
@rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-a4b-*
@touch $(DESTDIR)/lib/firmware/.dahdi-fw-a4b-$(A4B_VERSION)
else
@echo "Firmware dahdi-fw-a4b.bin is already installed with required version $(A4B_VERSION)"
endif
# Uninstall any installed dahdi firmware images from hotplug firmware directories
hotplug-uninstall:

4576
drivers/dahdi/wcaxx-base.c Normal file

File diff suppressed because it is too large Load Diff

919
drivers/dahdi/wcxb.c Normal file
View File

@ -0,0 +1,919 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/firmware.h>
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 26)
#define HAVE_RATELIMIT
#include <linux/ratelimit.h>
#endif
#include <dahdi/kernel.h>
#include <stdbool.h>
#include "wcxb.h"
#include "wcxb_spi.h"
#include "wcxb_flash.h"
/* The definition for Surprise Down was added in Linux 3.6 in (a0dee2e PCI: misc
* pci_reg additions). It may be backported though so we won't check for the
* version. */
#ifndef PCI_ERR_UNC_SURPDN
#define PCI_ERR_UNC_SURPDN 0x20
#endif
/* FPGA Status definitions */
#define OCT_CPU_RESET (1 << 0)
#define OCT_CPU_DRAM_CKE (1 << 1)
#define STATUS_LED_GREEN (1 << 9)
#define STATUS_LED_RED (1 << 10)
#define FALC_CPU_RESET (1 << 11)
/* Descriptor ring definitions */
#define DRING_SIZE (1 << 7) /* Must be in multiples of 2 */
#define DRING_SIZE_MASK (DRING_SIZE-1)
#define DESC_EOR (1 << 0)
#define DESC_INT (1 << 1)
#define DESC_OWN (1 << 31)
#define DESC_DEFAULT_STATUS 0xdeadbeef
#define DMA_CHAN_SIZE 128
/* Echocan definitions */
#define OCT_OFFSET (xb->membase + 0x10000)
#define OCT_CONTROL_REG (OCT_OFFSET + 0)
#define OCT_DATA_REG (OCT_OFFSET + 0x4)
#define OCT_ADDRESS_HIGH_REG (OCT_OFFSET + 0x8)
#define OCT_ADDRESS_LOW_REG (OCT_OFFSET + 0xa)
#define OCT_DIRECT_WRITE_MASK 0x3001
#define OCT_INDIRECT_READ_MASK 0x0101
#define OCT_INDIRECT_WRITE_MASK 0x3101
/* DMA definitions */
#define TDM_DRING_ADDR 0x2000
#define TDM_CONTROL (TDM_DRING_ADDR + 0x4)
#define ENABLE_ECHOCAN_TDM (1 << 0)
#define TDM_RECOVER_CLOCK (1 << 1)
#define ENABLE_DMA (1 << 2)
#define DMA_RUNNING (1 << 3)
#define DMA_LOOPBACK (1 << 4)
#define AUTHENTICATED (1 << 5)
#define TDM_VERSION (TDM_DRING_ADDR + 0x24)
/* Interrupt definitions */
#define INTERRUPT_CONTROL 0x300
#define ISR (INTERRUPT_CONTROL + 0x0)
#define IPR (INTERRUPT_CONTROL + 0x4)
#define IER (INTERRUPT_CONTROL + 0x8)
#define IAR (INTERRUPT_CONTROL + 0xc)
#define SIE (INTERRUPT_CONTROL + 0x10)
#define CIE (INTERRUPT_CONTROL + 0x14)
#define IVR (INTERRUPT_CONTROL + 0x18)
#define MER (INTERRUPT_CONTROL + 0x1c)
#define MER_ME (1<<0)
#define MER_HIE (1<<1)
#define DESC_UNDERRUN (1<<0)
#define DESC_COMPLETE (1<<1)
#define OCT_INT (1<<2)
#define FALC_INT (1<<3)
#define SPI_INT (1<<4)
#define FLASH_SPI_BASE 0x200
struct wcxb_hw_desc {
volatile __be32 status;
__be32 tx_buf;
__be32 rx_buf;
volatile __be32 control;
} __packed;
struct wcxb_meta_desc {
void *tx_buf_virt;
void *rx_buf_virt;
};
static inline bool wcxb_is_pcie(const struct wcxb *xb)
{
#ifndef WCXB_PCI_DEV_DOES_NOT_HAVE_IS_PCIE
return (xb->pdev->is_pcie > 0);
#else
return (xb->flags.is_pcie > 0);
#endif
}
static const unsigned int CLK_SRC_MASK = ((1 << 13) | (1 << 12) | (1 << 1));
enum wcxb_clock_sources wcxb_get_clksrc(struct wcxb *xb)
{
static const u32 SELF = 0x0;
static const u32 RECOVER = (1 << 1);
static const u32 SLAVE = (1 << 12) | (1 << 1);
unsigned long flags;
u32 reg;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase + TDM_CONTROL) & CLK_SRC_MASK;
spin_unlock_irqrestore(&xb->lock, flags);
if (SELF == reg)
return WCXB_CLOCK_SELF;
else if (RECOVER == reg)
return WCXB_CLOCK_RECOVER;
else if (SLAVE == reg)
return WCXB_CLOCK_SLAVE;
else
WARN_ON(1);
return WCXB_CLOCK_SELF;
}
void wcxb_set_clksrc(struct wcxb *xb, enum wcxb_clock_sources clksrc)
{
unsigned long flags;
u32 clkbits = 0;
switch (clksrc) {
case WCXB_CLOCK_RECOVER:
if (xb->flags.drive_timing_cable)
clkbits = (1<<13) | (1 << 1);
else
clkbits = (1 << 1);
break;
case WCXB_CLOCK_SELF:
if (xb->flags.drive_timing_cable)
clkbits = (1<<13);
else
clkbits = 0;
break;
case WCXB_CLOCK_SLAVE:
/* When we're slave, do not ever drive the timing cable. */
clkbits = (1<<12) | (1 << 1);
break;
};
/* set new clock select */
spin_lock_irqsave(&xb->lock, flags);
if (!wcxb_is_stopped(xb)) {
dev_err(&xb->pdev->dev, "ERROR: Cannot set clock source while DMA engine is running.\n");
} else {
u32 reg;
reg = ioread32be(xb->membase + TDM_CONTROL);
reg &= ~CLK_SRC_MASK;
reg |= (clkbits & CLK_SRC_MASK);
iowrite32be(reg, xb->membase + TDM_CONTROL);
}
spin_unlock_irqrestore(&xb->lock, flags);
}
void wcxb_enable_echocan(struct wcxb *xb)
{
u32 reg;
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase + TDM_CONTROL);
reg |= ENABLE_ECHOCAN_TDM;
iowrite32be(reg, xb->membase + TDM_CONTROL);
spin_unlock_irqrestore(&xb->lock, flags);
}
void wcxb_disable_echocan(struct wcxb *xb)
{
u32 reg;
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase + TDM_CONTROL);
reg &= ~ENABLE_ECHOCAN_TDM;
iowrite32be(reg, xb->membase + TDM_CONTROL);
spin_unlock_irqrestore(&xb->lock, flags);
}
void wcxb_reset_echocan(struct wcxb *xb)
{
unsigned long flags;
int reg;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase);
iowrite32be((reg & ~OCT_CPU_RESET), xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
msleep_interruptible(1);
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase);
iowrite32be((reg | OCT_CPU_RESET), xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
dev_dbg(&xb->pdev->dev, "Reset octasic\n");
}
bool wcxb_is_echocan_present(struct wcxb *xb)
{
return 0x1 == ioread16be(OCT_CONTROL_REG);
}
void wcxb_enable_echocan_dram(struct wcxb *xb)
{
unsigned long flags;
int reg;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase);
iowrite32be((reg | OCT_CPU_DRAM_CKE), xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
}
u16 wcxb_get_echocan_reg(struct wcxb *xb, u32 address)
{
uint16_t highaddress = ((address >> 20) & 0xfff);
uint16_t lowaddress = ((address >> 4) & 0xfffff);
unsigned long stop = jiffies + HZ/10;
unsigned long flags;
u16 ret;
spin_lock_irqsave(&xb->lock, flags);
iowrite16be(highaddress, OCT_ADDRESS_HIGH_REG);
iowrite16be(lowaddress, OCT_ADDRESS_LOW_REG);
iowrite16be(OCT_INDIRECT_READ_MASK | ((address & 0xe) << 8),
OCT_CONTROL_REG);
do {
ret = ioread16be(OCT_CONTROL_REG);
} while ((ret & (1<<8)) && time_before(jiffies, stop));
WARN_ON_ONCE(time_after_eq(jiffies, stop));
ret = ioread16be(OCT_DATA_REG);
spin_unlock_irqrestore(&xb->lock, flags);
return ret;
}
void wcxb_set_echocan_reg(struct wcxb *xb, u32 address, u16 val)
{
unsigned long flags;
uint16_t ret;
uint16_t highaddress = ((address >> 20) & 0xfff);
uint16_t lowaddress = ((address >> 4) & 0xffff);
unsigned long stop = jiffies + HZ/10;
spin_lock_irqsave(&xb->lock, flags);
iowrite16be(highaddress, OCT_ADDRESS_HIGH_REG);
iowrite16be(lowaddress, OCT_ADDRESS_LOW_REG);
iowrite16be(val, OCT_DATA_REG);
iowrite16be(OCT_INDIRECT_WRITE_MASK | ((address & 0xe) << 8),
OCT_CONTROL_REG);
/* No write should take longer than 100ms */
do {
ret = ioread16be(OCT_CONTROL_REG);
} while ((ret & (1<<8)) && time_before(jiffies, stop));
spin_unlock_irqrestore(&xb->lock, flags);
WARN_ON_ONCE(time_after_eq(jiffies, stop));
}
#ifdef HAVE_RATELIMIT
static DEFINE_RATELIMIT_STATE(_underrun_rl, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
#endif
/* wcxb_reset_dring needs to be called with xb->lock held. */
static void _wcxb_reset_dring(struct wcxb *xb)
{
int x;
struct wcxb_meta_desc *mdesc;
struct wcxb_hw_desc *hdesc = NULL;
xb->dma_head = xb->dma_tail = 0;
if (unlikely(xb->latency > DRING_SIZE)) {
#ifdef HAVE_RATELIMIT
if (__ratelimit(&_underrun_rl)) {
#else
if (printk_ratelimit()) {
#endif
dev_info(&xb->pdev->dev,
"Oops! Tried to increase latency past buffer size.\n");
}
xb->latency = DRING_SIZE;
}
for (x = 0; x < xb->latency; x++) {
dma_addr_t dma_tmp;
mdesc = &xb->meta_dring[x];
hdesc = &xb->hw_dring[x];
hdesc->status = cpu_to_be32(DESC_DEFAULT_STATUS);
if (!mdesc->tx_buf_virt) {
mdesc->tx_buf_virt =
dma_pool_alloc(xb->pool, GFP_ATOMIC, &dma_tmp);
hdesc->tx_buf = cpu_to_be32(dma_tmp);
mdesc->rx_buf_virt =
dma_pool_alloc(xb->pool, GFP_ATOMIC, &dma_tmp);
hdesc->rx_buf = cpu_to_be32(dma_tmp);
}
hdesc->control = cpu_to_be32(DESC_INT|DESC_OWN);
BUG_ON(!mdesc->tx_buf_virt || !mdesc->rx_buf_virt);
}
BUG_ON(!hdesc);
/* Set end of ring bit in last descriptor to force hw to loop around */
hdesc->control |= cpu_to_be32(DESC_EOR);
iowrite32be(xb->hw_dring_phys, xb->membase + TDM_DRING_ADDR);
}
static void wcxb_handle_dma(struct wcxb *xb)
{
struct wcxb_meta_desc *mdesc;
while (!(xb->hw_dring[xb->dma_tail].control & cpu_to_be32(DESC_OWN))) {
u_char *frame;
mdesc = &xb->meta_dring[xb->dma_tail];
frame = mdesc->rx_buf_virt;
xb->ops->handle_receive(xb, frame);
xb->dma_tail =
(xb->dma_tail == xb->latency-1) ? 0 : xb->dma_tail + 1;
mdesc = &xb->meta_dring[xb->dma_head];
frame = mdesc->tx_buf_virt;
xb->ops->handle_transmit(xb, frame);
wmb();
xb->hw_dring[xb->dma_head].control |= cpu_to_be32(DESC_OWN);
xb->dma_head =
(xb->dma_head == xb->latency-1) ? 0 : xb->dma_head + 1;
}
}
static irqreturn_t _wcxb_isr(int irq, void *dev_id)
{
struct wcxb *xb = dev_id;
unsigned int limit = 8;
u32 pending;
pending = ioread32be(xb->membase + ISR);
if (!pending)
return IRQ_NONE;
do {
iowrite32be(pending, xb->membase + IAR);
if (pending & DESC_UNDERRUN) {
u32 reg;
/* bump latency */
spin_lock(&xb->lock);
if (!xb->flags.latency_locked) {
xb->latency++;
#ifdef HAVE_RATELIMIT
if (__ratelimit(&_underrun_rl)) {
#else
if (printk_ratelimit()) {
#endif
dev_info(&xb->pdev->dev,
"Underrun detected by hardware. Latency bumped to: %dms\n",
xb->latency);
}
}
/* re-setup dma ring */
_wcxb_reset_dring(xb);
/* set dma enable bit */
reg = ioread32be(xb->membase + TDM_CONTROL);
reg |= ENABLE_DMA;
iowrite32be(reg, xb->membase + TDM_CONTROL);
spin_unlock(&xb->lock);
}
if (pending & DESC_COMPLETE) {
xb->framecount++;
wcxb_handle_dma(xb);
}
if (NULL != xb->ops->handle_interrupt)
xb->ops->handle_interrupt(xb, pending);
pending = ioread32be(xb->membase + ISR);
} while (pending && --limit);
return IRQ_HANDLED;
}
DAHDI_IRQ_HANDLER(wcxb_isr)
{
irqreturn_t ret;
unsigned long flags;
local_irq_save(flags);
ret = _wcxb_isr(irq, dev_id);
local_irq_restore(flags);
return ret;
}
static int wcxb_alloc_dring(struct wcxb *xb, const char *board_name)
{
xb->meta_dring =
kzalloc(sizeof(struct wcxb_meta_desc) * DRING_SIZE,
GFP_KERNEL);
if (!xb->meta_dring)
return -ENOMEM;
xb->hw_dring = dma_alloc_coherent(&xb->pdev->dev,
sizeof(struct wcxb_hw_desc) * DRING_SIZE,
&xb->hw_dring_phys,
GFP_KERNEL);
if (!xb->hw_dring) {
kfree(xb->meta_dring);
return -ENOMEM;
}
xb->pool = dma_pool_create(board_name, &xb->pdev->dev,
PAGE_SIZE, PAGE_SIZE, 0);
if (!xb->pool) {
kfree(xb->meta_dring);
dma_free_coherent(&xb->pdev->dev,
sizeof(struct wcxb_hw_desc) * DRING_SIZE,
xb->hw_dring,
xb->hw_dring_phys);
return -ENOMEM;
}
return 0;
}
/**
* wcxb_soft_reset - Set interface registers back to known good values.
*
* This represents the normal default state after a reset of the FPGA. This
* function is preferred over the hard reset function.
*
*/
static void wcxb_soft_reset(struct wcxb *xb)
{
/* digium_gpo */
iowrite32be(0x0, xb->membase);
/* xps_intc */
iowrite32be(0x0, xb->membase + 0x300);
iowrite32be(0x0, xb->membase + 0x308);
iowrite32be(0x0, xb->membase + 0x310);
iowrite32be(0x0, xb->membase + 0x31C);
/* xps_spi_config_flash */
iowrite32be(0xA, xb->membase + 0x200);
/* tdm engine */
iowrite32be(0x0, xb->membase + 0x2000);
iowrite32be(0x0, xb->membase + 0x2004);
}
static void _wcxb_hard_reset(struct wcxb *xb)
{
struct pci_dev *const pdev = xb->pdev;
u32 microblaze_version;
unsigned long stop_time = jiffies + msecs_to_jiffies(2000);
pci_save_state(pdev);
iowrite32be(0xe00, xb->membase + TDM_CONTROL);
/* This sleep is to give FPGA time to bring up the PCI/PCIe interface */
msleep(200);
pci_restore_state(pdev);
/* Wait for the Microblaze CPU to complete it's startup */
do {
msleep(20);
/* Can return either 0xffff or 0 before it's fully booted */
microblaze_version = ioread32be(xb->membase + 0x2018) ?: 0xffff;
} while (time_before(jiffies, stop_time)
&& 0xffff == microblaze_version);
}
/*
* Since the FPGA hard reset drops the PCIe link we need to disable
* error reporting on the upsteam link. Otherwise Surprise Down errors
* may be reported in reponse to the link going away.
*
* NOTE: We cannot use pci_disable_pcie_error_reporting() because it will not
* disable error reporting if the system firmware is attached to the advanced
* error reporting mechanism.
*/
static void _wcxb_pcie_hard_reset(struct wcxb *xb)
{
struct pci_dev *const parent = xb->pdev->bus->self;
u32 aer_mask;
int pos;
if (!wcxb_is_pcie(xb))
return;
pos = pci_find_ext_capability(parent, PCI_EXT_CAP_ID_ERR);
if (pos) {
pci_read_config_dword(parent, pos + PCI_ERR_UNCOR_MASK,
&aer_mask);
pci_write_config_dword(parent, pos + PCI_ERR_UNCOR_MASK,
aer_mask | PCI_ERR_UNC_SURPDN);
}
_wcxb_hard_reset(xb);
if (pos) {
pci_write_config_dword(parent, pos + PCI_ERR_UNCOR_MASK,
aer_mask);
/* Clear the error as well from the status register. */
pci_write_config_dword(parent, pos + PCI_ERR_UNCOR_STATUS,
PCI_ERR_UNC_SURPDN);
}
return;
}
/**
* wcxb_hard_reset - Reset FPGA and reload firmware.
*
* This may be called in the context of device probe and therefore the PCI
* device may be locked.
*
*/
static void wcxb_hard_reset(struct wcxb *xb)
{
if (wcxb_is_pcie(xb))
_wcxb_pcie_hard_reset(xb);
else
_wcxb_hard_reset(xb);
}
int wcxb_init(struct wcxb *xb, const char *board_name, u32 int_mode)
{
int res = 0;
struct pci_dev *pdev = xb->pdev;
u32 tdm_control;
if (pci_enable_device(pdev))
return -EIO;
pci_set_master(pdev);
#ifdef WCXB_PCI_DEV_DOES_NOT_HAVE_IS_PCIE
xb->flags.is_pcie = pci_find_capability(pdev, PCI_CAP_ID_EXP) ? 1 : 0;
#endif
WARN_ON(!pdev);
if (!pdev)
return -EINVAL;
xb->latency = WCXB_DEFAULT_LATENCY;
spin_lock_init(&xb->lock);
xb->membase = pci_iomap(pdev, 0, 0);
if (pci_request_regions(pdev, board_name))
dev_info(&xb->pdev->dev, "Unable to request regions\n");
wcxb_soft_reset(xb);
res = wcxb_alloc_dring(xb, board_name);
if (res) {
dev_err(&xb->pdev->dev,
"Failed to allocate descriptor rings.\n");
goto fail_exit;
}
/* Enable writes to fpga status register */
iowrite32be(0, xb->membase + 0x04);
xb->flags.have_msi = (int_mode) ? 0 : (0 == pci_enable_msi(pdev));
if (request_irq(pdev->irq, wcxb_isr,
(xb->flags.have_msi) ? 0 : DAHDI_IRQ_SHARED,
board_name, xb)) {
dev_notice(&xb->pdev->dev, "Unable to request IRQ %d\n",
pdev->irq);
res = -EIO;
goto fail_exit;
}
iowrite32be(0, xb->membase + TDM_CONTROL);
tdm_control = ioread32be(xb->membase + TDM_CONTROL);
if (!(tdm_control & 0x20)) {
dev_err(&xb->pdev->dev,
"This board is not authenticated and may not function properly.\n");
msleep(1000);
} else {
dev_dbg(&xb->pdev->dev, "Authenticated. %08x\n", tdm_control);
}
return res;
fail_exit:
pci_release_regions(xb->pdev);
return res;
}
void wcxb_stop_dma(struct wcxb *xb)
{
unsigned long flags;
u32 reg;
/* Quiesce DMA engine interrupts */
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase + TDM_CONTROL);
reg &= ~ENABLE_DMA;
iowrite32be(reg, xb->membase + TDM_CONTROL);
spin_unlock_irqrestore(&xb->lock, flags);
}
int wcxb_wait_for_stop(struct wcxb *xb, unsigned long timeout_ms)
{
unsigned long stop;
stop = jiffies + msecs_to_jiffies(timeout_ms);
do {
if (time_after(jiffies, stop))
return -EIO;
else
cpu_relax();
} while (!wcxb_is_stopped(xb));
return 0;
}
void wcxb_disable_interrupts(struct wcxb *xb)
{
iowrite32be(0, xb->membase + IER);
}
void wcxb_stop(struct wcxb *xb)
{
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
/* Stop everything */
iowrite32be(0, xb->membase + TDM_CONTROL);
iowrite32be(0, xb->membase + IER);
iowrite32be(0, xb->membase + MER);
iowrite32be(-1, xb->membase + IAR);
/* Flush quiesce commands before exit */
ioread32be(xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
synchronize_irq(xb->pdev->irq);
}
bool wcxb_is_stopped(struct wcxb *xb)
{
return !(ioread32be(xb->membase + TDM_CONTROL) & DMA_RUNNING);
}
static void wcxb_free_dring(struct wcxb *xb)
{
struct wcxb_meta_desc *mdesc;
struct wcxb_hw_desc *hdesc;
int i;
/* Free tx/rx buffs */
for (i = 0; i < DRING_SIZE; i++) {
mdesc = &xb->meta_dring[i];
hdesc = &xb->hw_dring[i];
if (mdesc->tx_buf_virt) {
dma_pool_free(xb->pool,
mdesc->tx_buf_virt,
be32_to_cpu(hdesc->tx_buf));
dma_pool_free(xb->pool,
mdesc->rx_buf_virt,
be32_to_cpu(hdesc->rx_buf));
}
}
dma_pool_destroy(xb->pool);
dma_free_coherent(&xb->pdev->dev,
sizeof(struct wcxb_hw_desc) * DRING_SIZE,
xb->hw_dring,
xb->hw_dring_phys);
kfree(xb->meta_dring);
}
void wcxb_release(struct wcxb *xb)
{
wcxb_stop(xb);
synchronize_irq(xb->pdev->irq);
free_irq(xb->pdev->irq, xb);
if (xb->flags.have_msi)
pci_disable_msi(xb->pdev);
if (xb->membase)
pci_iounmap(xb->pdev, xb->membase);
wcxb_free_dring(xb);
pci_release_regions(xb->pdev);
pci_disable_device(xb->pdev);
return;
}
int wcxb_start(struct wcxb *xb)
{
u32 reg;
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
_wcxb_reset_dring(xb);
/* Enable hardware interrupts */
iowrite32be(-1, xb->membase + IAR);
iowrite32be(DESC_UNDERRUN|DESC_COMPLETE, xb->membase + IER);
/* iowrite32be(0x3f7, xb->membase + IER); */
iowrite32be(MER_ME|MER_HIE, xb->membase + MER);
/* Start the DMA engine processing. */
reg = ioread32be(xb->membase + TDM_CONTROL);
reg |= ENABLE_DMA;
iowrite32be(reg, xb->membase + TDM_CONTROL);
spin_unlock_irqrestore(&xb->lock, flags);
return 0;
}
struct wcxb_firm_header {
u8 header[6];
__le32 chksum;
u8 pad[18];
__le32 version;
} __packed;
static u32 wcxb_get_firmware_version(struct wcxb *xb)
{
u32 version = 0;
/* Two version registers are read and catenated into one */
/* Firmware version goes in bits upper byte */
version = ((ioread32be(xb->membase + 0x400) & 0xffff)<<16);
/* Microblaze version goes in lower word */
version += ioread32be(xb->membase + 0x2018);
return version;
}
static int wcxb_update_firmware(struct wcxb *xb, const struct firmware *fw,
const char *filename)
{
u32 tdm_control;
int offset = 0x200000;
const u8 *data, *end;
struct wcxb_spi_master *flash_spi_master;
struct wcxb_spi_device *flash_spi_device;
flash_spi_master = wcxb_spi_master_create(&xb->pdev->dev,
xb->membase + FLASH_SPI_BASE,
false);
flash_spi_device = wcxb_spi_device_create(flash_spi_master, 0);
dev_info(&xb->pdev->dev,
"Uploading %s. This can take up to 30 seconds.\n", filename);
data = &fw->data[sizeof(struct wcxb_firm_header)];
end = &fw->data[fw->size];
while (data < end) {
wcxb_flash_sector_erase(flash_spi_device, offset);
data += 0x10000;
offset += 0x10000;
}
data = &fw->data[sizeof(struct wcxb_firm_header)];
offset = 0x200000;
wcxb_flash_write(flash_spi_device, offset, data, end-data);
/* Reset fpga after loading firmware */
dev_info(&xb->pdev->dev, "Firmware load complete. Reseting device.\n");
tdm_control = ioread32be(xb->membase + TDM_CONTROL);
wcxb_hard_reset(xb);
iowrite32be(0, xb->membase + 0x04);
iowrite32be(tdm_control, xb->membase + TDM_CONTROL);
wcxb_spi_device_destroy(flash_spi_device);
wcxb_spi_master_destroy(flash_spi_master);
return 0;
}
int wcxb_check_firmware(struct wcxb *xb, const u32 expected_version,
const char *firmware_filename, bool force_firmware)
{
const struct firmware *fw;
const struct wcxb_firm_header *header;
int res = 0;
u32 crc;
u32 version = 0;
version = wcxb_get_firmware_version(xb);
if (0xff000000 == (version & 0xff000000)) {
dev_info(&xb->pdev->dev,
"Invalid firmware %x. Please check your hardware.\n",
version);
return -EIO;
}
if ((expected_version == version) && !force_firmware) {
dev_info(&xb->pdev->dev, "Firmware version: %x\n", version);
return 0;
}
if (force_firmware) {
dev_info(&xb->pdev->dev,
"force_firmware module parameter is set. Forcing firmware load, regardless of version\n");
} else {
dev_info(&xb->pdev->dev,
"Firmware version %x is running, but we require version %x.\n",
version, expected_version);
}
res = request_firmware(&fw, firmware_filename, &xb->pdev->dev);
if (res) {
dev_info(&xb->pdev->dev,
"Firmware '%s' not available from userspace.\n",
firmware_filename);
goto cleanup;
}
header = (const struct wcxb_firm_header *)fw->data;
/* Check the crc */
crc = crc32(~0, &fw->data[10], fw->size - 10) ^ ~0;
if (memcmp("DIGIUM", header->header, sizeof(header->header)) ||
(le32_to_cpu(header->chksum) != crc)) {
dev_info(&xb->pdev->dev,
"%s is invalid. Please reinstall.\n",
firmware_filename);
goto cleanup;
}
/* Check the file vs required firmware versions */
if (le32_to_cpu(header->version) != expected_version) {
dev_err(&xb->pdev->dev,
"Existing firmware file %s is version %x, but we require %x. Please install the correct firmware file.\n",
firmware_filename, le32_to_cpu(header->version),
expected_version);
res = -EIO;
goto cleanup;
}
dev_info(&xb->pdev->dev, "Found %s (version: %x) Preparing for flash\n",
firmware_filename, header->version);
res = wcxb_update_firmware(xb, fw, firmware_filename);
version = wcxb_get_firmware_version(xb);
dev_info(&xb->pdev->dev, "Reset into firmware version: %x\n", version);
if ((expected_version != version) && !force_firmware) {
/* On the off chance that the interface is in a state where it
* cannot boot into the updated firmware image, power cycling
* the card can recover. A simple "reset" of the computer is not
* sufficient, power has to be removed completely. */
dev_err(&xb->pdev->dev,
"The wrong firmware is running after update. Please power cycle and try again.\n");
res = -EIO;
goto cleanup;
}
if (res) {
dev_info(&xb->pdev->dev,
"Failed to load firmware %s\n", firmware_filename);
}
cleanup:
release_firmware(fw);
return res;
}

184
drivers/dahdi/wcxb.h Normal file
View File

@ -0,0 +1,184 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#ifndef __WCXB_H__
#define __WCXB_H__
#define WCXB_DEFAULT_LATENCY 3U
#define WCXB_DEFAULT_MAXLATENCY 20U
#define WCXB_DMA_CHAN_SIZE 128
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
/* The is_pcie member was backported but I'm not sure in which version. */
# ifndef RHEL_RELEASE_VERSION
#define WCXB_PCI_DEV_DOES_NOT_HAVE_IS_PCIE
# endif
#else
#endif
struct wcxb;
struct wcxb_operations {
void (*handle_receive)(struct wcxb *xb, void *frame);
void (*handle_transmit)(struct wcxb *xb, void *frame);
void (*handle_error)(struct wcxb *xb);
void (*handle_interrupt)(struct wcxb *xb, u32 pending);
};
struct wcxb_meta_desc;
struct wcxb_hw_desc;
struct wcxb {
struct pci_dev *pdev;
spinlock_t lock;
const struct wcxb_operations *ops;
unsigned int *debug;
unsigned int max_latency;
unsigned int latency;
struct {
u32 have_msi:1;
u32 latency_locked:1;
u32 drive_timing_cable:1;
#ifdef WCXB_PCI_DEV_DOES_NOT_HAVE_IS_PCIE
u32 is_pcie:1;
#endif
} flags;
void __iomem *membase;
struct wcxb_meta_desc *meta_dring;
struct wcxb_hw_desc *hw_dring;
unsigned int dma_head;
unsigned int dma_tail;
dma_addr_t hw_dring_phys;
struct dma_pool *pool;
unsigned long framecount;
};
extern int wcxb_init(struct wcxb *xb, const char *board_name, u32 int_mode);
extern void wcxb_release(struct wcxb *xb);
extern int wcxb_start(struct wcxb *xb);
extern void wcxb_stop(struct wcxb *xb);
extern int wcxb_wait_for_stop(struct wcxb *xb, unsigned long timeout_ms);
extern bool wcxb_is_stopped(struct wcxb *xb);
enum wcxb_clock_sources {
WCXB_CLOCK_SELF, /* Use the internal oscillator for timing. */
WCXB_CLOCK_RECOVER, /* Recover the clock from a framer. */
#ifdef RPC_RCLK
WCXB_CLOCK_RECOVER_ALT, /* Recover the clock from a framer. */
#endif
WCXB_CLOCK_SLAVE /* Recover clock from any timing header. */
};
extern enum wcxb_clock_sources wcxb_get_clksrc(struct wcxb *xb);
extern void wcxb_set_clksrc(struct wcxb *xb, enum wcxb_clock_sources clksrc);
static inline void wcxb_enable_timing_header_driver(struct wcxb *xb)
{
xb->flags.drive_timing_cable = 1;
}
static inline bool wcxb_is_timing_header_driver_enabled(struct wcxb *xb)
{
return 1 == xb->flags.drive_timing_cable;
}
static inline void wcxb_disable_timing_header_driver(struct wcxb *xb)
{
xb->flags.drive_timing_cable = 0;
}
extern int wcxb_check_firmware(struct wcxb *xb, const u32 expected_version,
const char *firmware_filename,
bool force_firmware);
extern void wcxb_stop_dma(struct wcxb *xb);
extern void wcxb_disable_interrupts(struct wcxb *xb);
static inline void wcxb_gpio_set(struct wcxb *xb, u32 bits)
{
u32 reg;
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase);
iowrite32be(reg | bits, xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
}
static inline void wcxb_gpio_clear(struct wcxb *xb, u32 bits)
{
u32 reg;
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
reg = ioread32be(xb->membase);
iowrite32be(reg & (~bits), xb->membase);
spin_unlock_irqrestore(&xb->lock, flags);
}
static inline void
wcxb_set_maxlatency(struct wcxb *xb, unsigned int max_latency)
{
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
xb->max_latency = clamp(max_latency,
xb->latency,
WCXB_DEFAULT_MAXLATENCY);
spin_unlock_irqrestore(&xb->lock, flags);
}
static inline void
wcxb_set_minlatency(struct wcxb *xb, unsigned int min_latency)
{
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
xb->latency = clamp(min_latency, WCXB_DEFAULT_LATENCY,
WCXB_DEFAULT_MAXLATENCY);
spin_unlock_irqrestore(&xb->lock, flags);
}
static inline void
wcxb_lock_latency(struct wcxb *xb)
{
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
xb->flags.latency_locked = 1;
spin_unlock_irqrestore(&xb->lock, flags);
return;
}
static inline void
wcxb_unlock_latency(struct wcxb *xb)
{
unsigned long flags;
spin_lock_irqsave(&xb->lock, flags);
xb->flags.latency_locked = 0;
spin_unlock_irqrestore(&xb->lock, flags);
return;
}
/* Interface for the echocan block */
extern void wcxb_enable_echocan(struct wcxb *xb);
extern void wcxb_disable_echocan(struct wcxb *xb);
extern void wcxb_reset_echocan(struct wcxb *xb);
extern void wcxb_enable_echocan_dram(struct wcxb *xb);
extern bool wcxb_is_echocan_present(struct wcxb *xb);
extern u16 wcxb_get_echocan_reg(struct wcxb *xb, u32 address);
extern void wcxb_set_echocan_reg(struct wcxb *xb, u32 address, u16 val);
#endif

170
drivers/dahdi/wcxb_flash.c Normal file
View File

@ -0,0 +1,170 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/sched.h>
#include "wcxb_spi.h"
#include "wcxb_flash.h"
#define FLASH_PAGE_PROGRAM 0x02
#define FLASH_READ 0x03
#define FLASH_READ_STATUS 0x05
#define FLASH_WRITE_ENABLE 0x06
#define FLASH_SECTOR_ERASE 0xd8
static int wcxb_flash_read_status_register(struct wcxb_spi_device *spi,
u8 *status)
{
u8 command[] = {
FLASH_READ_STATUS,
};
struct wcxb_spi_transfer t_cmd = {
.tx_buf = command,
.len = sizeof(command),
};
struct wcxb_spi_transfer t_serial = {
.rx_buf = status,
.len = 1,
};
struct wcxb_spi_message m;
wcxb_spi_message_init(&m);
wcxb_spi_message_add_tail(&t_cmd, &m);
wcxb_spi_message_add_tail(&t_serial, &m);
return wcxb_spi_sync(spi, &m);
}
int wcxb_flash_read(struct wcxb_spi_device *spi, unsigned int address,
void *data, size_t len)
{
u8 command[] = {
FLASH_READ,
(address & 0xff0000) >> 16,
(address & 0xff00) >> 8,
(address & 0xff)
};
struct wcxb_spi_transfer t_cmd = {
.tx_buf = command,
.len = sizeof(command),
};
struct wcxb_spi_transfer t_serial = {
.rx_buf = data,
.len = len,
};
struct wcxb_spi_message m;
wcxb_spi_message_init(&m);
wcxb_spi_message_add_tail(&t_cmd, &m);
wcxb_spi_message_add_tail(&t_serial, &m);
return wcxb_spi_sync(spi, &m);
}
static int wcxb_flash_wait_until_not_busy(struct wcxb_spi_device *spi)
{
int res;
u8 status;
unsigned long stop = jiffies + 5*HZ;
do {
res = wcxb_flash_read_status_register(spi, &status);
} while (!res && (status & 0x1) && time_before(jiffies, stop));
if (!res)
return res;
if (time_after_eq(jiffies, stop))
return -EIO;
return 0;
}
static int wcxb_flash_write_enable(struct wcxb_spi_device *spi)
{
u8 command = FLASH_WRITE_ENABLE;
return wcxb_spi_write(spi, &command, 1);
}
int wcxb_flash_sector_erase(struct wcxb_spi_device *spi,
unsigned int address)
{
int res;
u8 command[] = {FLASH_SECTOR_ERASE, (address >> 16)&0xff, 0x00, 0x00};
/* Sector must be on 64KB boundary. */
if (address & 0xffff)
return -EINVAL;
/* Start the erase. */
res = wcxb_flash_write_enable(spi);
if (res)
return res;
res = wcxb_spi_write(spi, &command, sizeof(command));
if (res)
return res;
return wcxb_flash_wait_until_not_busy(spi);
}
int wcxb_flash_write(struct wcxb_spi_device *spi, unsigned int address,
const void *data, size_t len)
{
int res;
const size_t FLASH_PAGE_SIZE = 256;
u8 command[] = {
FLASH_PAGE_PROGRAM,
(address & 0xff0000) >> 16,
(address & 0xff00) >> 8,
0x00,
};
struct wcxb_spi_transfer t_cmd = {
.tx_buf = command,
.len = sizeof(command),
};
struct wcxb_spi_transfer t_data = {
.tx_buf = data,
.len = len,
};
struct wcxb_spi_message m;
/* We need to write on page size boundaries */
WARN_ON(address & 0xff);
wcxb_spi_message_init(&m);
wcxb_spi_message_add_tail(&t_cmd, &m);
wcxb_spi_message_add_tail(&t_data, &m);
while (len) {
res = wcxb_flash_write_enable(spi);
if (res)
return res;
command[1] = (address >> 16) & 0xff;
command[2] = (address >> 8) & 0xff;
t_data.tx_buf = data;
t_data.len = min(len, FLASH_PAGE_SIZE);
res = wcxb_spi_sync(spi, &m);
WARN_ON(res);
if (res)
return res;
res = wcxb_flash_wait_until_not_busy(spi);
WARN_ON(res);
if (res)
return res;
len -= t_data.len;
address += t_data.len;
data += t_data.len;
}
return 0;
}

View File

@ -0,0 +1,34 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#ifndef __WCXB_FLASH_H__
#define __WCXB_FLASH_H__
extern int wcxb_flash_read(struct wcxb_spi_device *spi, unsigned int address,
void *data, size_t len);
extern int wcxb_flash_sector_erase(struct wcxb_spi_device *spi, unsigned int
address);
extern int wcxb_flash_write(struct wcxb_spi_device *spi, unsigned int address,
const void *data, size_t len);
#endif

382
drivers/dahdi/wcxb_spi.c Normal file
View File

@ -0,0 +1,382 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#define DEBUG
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/completion.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <dahdi/kernel.h>
#include "wcxb_spi.h"
#define BBIT(n) (1UL << (31UL - (n)))
#define SPISRR 0x40
#define SPISRR_RESET 0x0000000a /* Resets Device */
#define SPICR 0x60
#define SPICR_LSB_FIRST BBIT(22) /* LSB First. 0=MSB first transfer */
#define SPICR_MASTER_INHIBIT BBIT(23) /* Master Transaction Inhibit */
#define SPICR_SLAVE_SELECT BBIT(24) /* Manual Slave Select Assert Enable */
#define SPICR_RX_FIFO_RESET BBIT(25) /* Receive FIFO Reset */
#define SPICR_TX_FIFO_RESET BBIT(26) /* Transmit FIFO Reset */
#define SPICR_CPHA BBIT(27) /* Clock Phase */
#define SPICR_CPOL BBIT(28) /* Clock Polarity 0=Active High */
#define SPICR_MASTER BBIT(29) /* Master Enable */
#define SPICR_SPE BBIT(30) /* SPI System Enable */
#define SPICR_LOOP BBIT(31) /* Local Loopback Mode */
#define SPICR_START_TRANSFER (SPICR_CPHA | SPICR_CPOL | \
SPICR_MASTER | SPICR_SPE)
#define SPICR_READY_TRANSFER (SPICR_MASTER_INHIBIT | SPICR_START_TRANSFER)
#define SPISR 0x64 /* SPI Status Register */
#define SPISR_SLAVE_MODE_SEL BBIT(26) /* Slave Mode Select Flag */
#define SPISR_MODF BBIT(27) /* Mode-Fault Error Flag */
#define SPISR_TX_FULL BBIT(28) /* Transmit FIFO Full */
#define SPISR_TX_EMPTY BBIT(29) /* Transmit FIFO Empty */
#define SPISR_RX_FULL BBIT(30) /* Receive FIFO Full */
#define SPISR_RX_EMPTY BBIT(31) /* Receive FIFO Empty */
#define SPIDTR 0x68 /* SPI Data Transmit Register */
#define SPIDRR 0x6c /* SPI Data Receive Register */
#define SPISSR 0x70 /* SPI Slave Select Register */
#undef SUCCESS
#define SUCCESS 0
struct wcxb_spi_master {
struct device *parent;
struct list_head message_queue;
spinlock_t lock;
void __iomem *base;
struct wcxb_spi_message *cur_msg;
struct wcxb_spi_transfer *cur_transfer;
u16 bytes_left;
u16 bytes_in_fifo;
const u8 *cur_tx_byte;
u8 *cur_rx_byte;
u16 auto_cs:1;
};
static inline void _wcxb_assert_chip_select(struct wcxb_spi_master *master,
unsigned int cs)
{
const int cs_mask = ~(1UL << cs);
iowrite32be(cs_mask, master->base + SPISSR);
ioread32be(master->base + SPISSR);
}
static inline void _wcxb_clear_chip_select(struct wcxb_spi_master *master)
{
iowrite32be(~(0), master->base + SPISSR);
ioread32(master->base + SPISSR);
}
static inline void wcxb_spi_reset_controller(struct wcxb_spi_master *master)
{
u32 spicr = SPICR_READY_TRANSFER;
spicr |= (master->auto_cs) ? 0 : SPICR_SLAVE_SELECT;
iowrite32be(SPISRR_RESET, master->base + SPISRR);
iowrite32be(spicr, master->base + SPICR);
iowrite32be(0xffffffff, master->base + SPISSR);
}
struct wcxb_spi_master *wcxb_spi_master_create(struct device *parent,
void __iomem *membase,
bool auto_cs)
{
struct wcxb_spi_master *master = NULL;
master = kzalloc(sizeof(struct wcxb_spi_master), GFP_KERNEL);
if (!master)
goto error_exit;
spin_lock_init(&master->lock);
INIT_LIST_HEAD(&master->message_queue);
master->base = membase;
master->parent = parent;
master->auto_cs = (auto_cs) ? 1 : 0;
wcxb_spi_reset_controller(master);
return master;
error_exit:
kfree(master);
return NULL;
}
void wcxb_spi_master_destroy(struct wcxb_spi_master *master)
{
struct wcxb_spi_message *m;
if (!master)
return;
while (!list_empty(&master->message_queue)) {
m = list_first_entry(&master->message_queue,
struct wcxb_spi_message, node);
list_del(&m->node);
if (m->complete)
m->complete(m->arg);
}
kfree(master);
return;
}
static inline bool is_txfifo_empty(const struct wcxb_spi_master *master)
{
return ((ioread32(master->base + SPISR) &
cpu_to_be32(SPISR_TX_EMPTY)) > 0);
}
static const u8 DUMMY_TX = 0xff;
static u8 DUMMY_RX;
static void _wcxb_spi_transfer_to_fifo(struct wcxb_spi_master *master)
{
const unsigned int FIFO_SIZE = 16;
u32 spicr;
while (master->bytes_left && master->bytes_in_fifo < FIFO_SIZE) {
iowrite32be(*master->cur_tx_byte, master->base + SPIDTR);
master->bytes_in_fifo++;
master->bytes_left--;
if (&DUMMY_TX != master->cur_tx_byte)
master->cur_tx_byte++;
}
spicr = (master->auto_cs) ? SPICR_START_TRANSFER :
SPICR_START_TRANSFER | SPICR_SLAVE_SELECT;
iowrite32be(spicr, master->base + SPICR);
}
static void _wcxb_spi_transfer_from_fifo(struct wcxb_spi_master *master)
{
u32 spicr;
while (master->bytes_in_fifo) {
*master->cur_rx_byte = ioread32be(master->base + SPIDRR);
if (&DUMMY_RX != master->cur_rx_byte)
master->cur_rx_byte++;
--master->bytes_in_fifo;
}
spicr = SPICR_START_TRANSFER;
spicr |= (master->auto_cs) ? 0 : SPICR_SLAVE_SELECT;
iowrite32be(spicr | SPICR_MASTER_INHIBIT, master->base + SPICR);
}
static void _wcxb_spi_start_transfer(struct wcxb_spi_master *master,
struct wcxb_spi_transfer *t)
{
#ifdef DEBUG
if (!t || !master || (!t->tx_buf && !t->rx_buf) ||
master->cur_transfer) {
WARN_ON(1);
return;
}
#endif
master->cur_transfer = t;
master->bytes_left = t->len;
master->cur_tx_byte = (t->tx_buf) ?: &DUMMY_TX;
master->cur_rx_byte = (t->rx_buf) ?: &DUMMY_RX;
_wcxb_spi_transfer_to_fifo(master);
}
/**
* _wcxb_spi_start_message - Start a new message transferring.
*
* Must be called with master->lock held.
*
*/
static int _wcxb_spi_start_message(struct wcxb_spi_master *master,
struct wcxb_spi_message *message)
{
struct wcxb_spi_transfer *t;
if (master->cur_msg) {
/* There is already a message in progress. Queue for later. */
list_add_tail(&message->node, &master->message_queue);
return 0;
}
if (!message->spi) {
dev_dbg(master->parent,
"Queueing message without SPI device specified?\n");
return -EINVAL;
};
master->cur_msg = message;
_wcxb_assert_chip_select(master, message->spi->chip_select);
t = list_first_entry(&message->transfers,
struct wcxb_spi_transfer, node);
_wcxb_spi_start_transfer(master, t);
return 0;
}
/**
* wcxb_spi_complete_message - Complete the current message.
*
* Called after all transfers in current message have been completed. This will
* complete the current message and start the next queued message if there are
* any.
*
* Must be called with the master->lock held.
*
*/
static void _wcxb_spi_complete_cur_msg(struct wcxb_spi_master *master)
{
struct wcxb_spi_message *message;
if (!master->cur_msg)
return;
message = master->cur_msg;
message->status = SUCCESS;
_wcxb_clear_chip_select(master);
master->cur_msg = NULL;
if (!list_empty(&master->message_queue)) {
message = list_first_entry(&master->message_queue,
struct wcxb_spi_message, node);
list_del(&message->node);
_wcxb_spi_start_message(master, message);
}
return;
}
static inline bool
_wcxb_spi_is_last_transfer(const struct wcxb_spi_transfer *t,
const struct wcxb_spi_message *message)
{
return t->node.next == &message->transfers;
}
static inline struct wcxb_spi_transfer *
_wcxb_spi_next_transfer(struct wcxb_spi_transfer *t)
{
return list_entry(t->node.next, struct wcxb_spi_transfer, node);
}
/**
* wcxb_spi_handle_interrupt - Drives the transfers forward.
*
* Doesn't necessarily need to be called in the context of a real interrupt, but
* should be called with interrupts disabled on the local CPU.
*
*/
void wcxb_spi_handle_interrupt(struct wcxb_spi_master *master)
{
struct wcxb_spi_message *msg = master->cur_msg;
struct wcxb_spi_transfer *t = master->cur_transfer;
void (*complete)(void *arg) = NULL;
unsigned long flags;
/* Check if we're not in the middle of a transfer, or not finished with
* a part of one. */
spin_lock_irqsave(&master->lock, flags);
if (!msg || !is_txfifo_empty(master))
goto done;
#ifdef DEBUG
if (!t) {
dev_dbg(master->parent,
"No current transfer in %s\n", __func__);
goto done;
}
#endif
/* First read any data out of the receive FIFO into the current
* transfer. */
_wcxb_spi_transfer_from_fifo(master);
if (master->bytes_left) {
/* The current transfer isn't finished. */
_wcxb_spi_transfer_to_fifo(master);
goto done;
}
/* The current transfer is finished. Check for another transfer in this
* message or complete it and look for another message to start. */
master->cur_transfer = NULL;
if (_wcxb_spi_is_last_transfer(t, msg)) {
complete = msg->complete;
_wcxb_spi_complete_cur_msg(master);
} else {
t = _wcxb_spi_next_transfer(t);
_wcxb_spi_start_transfer(master, t);
}
done:
spin_unlock_irqrestore(&master->lock, flags);
/* Do not call the complete call back under the bus lock. */
if (complete)
complete(msg->arg);
return;
}
int wcxb_spi_async(struct wcxb_spi_device *spi,
struct wcxb_spi_message *message)
{
int res;
unsigned long flags;
WARN_ON(!spi || !message || !spi->master);
if (list_empty(&message->transfers)) {
/* No transfers in this message? */
if (message->complete)
message->complete(message->arg);
message->status = -EINVAL;
return 0;
}
message->status = -EINPROGRESS;
message->spi = spi;
spin_lock_irqsave(&spi->master->lock, flags);
res = _wcxb_spi_start_message(spi->master, message);
spin_unlock_irqrestore(&spi->master->lock, flags);
return res;
}
static void wcxb_spi_complete_message(void *arg)
{
complete((struct completion *)arg);
}
int wcxb_spi_sync(struct wcxb_spi_device *spi, struct wcxb_spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
WARN_ON(!spi || !spi->master);
message->complete = wcxb_spi_complete_message;
message->arg = &done;
wcxb_spi_async(spi, message);
/* TODO: There has got to be a better way to do this. */
while (!try_wait_for_completion(&done)) {
wcxb_spi_handle_interrupt(spi->master);
cpu_relax();
}
return message->status;
}

116
drivers/dahdi/wcxb_spi.h Normal file
View File

@ -0,0 +1,116 @@
/*
* wcxb SPI library
*
* Copyright (C) 2013 Digium, Inc.
*
* All rights reserved.
*
*/
/*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2 as published by the
* Free Software Foundation. See the LICENSE file included with
* this program for more details.
*/
#ifndef __WCXB_SPI_H
#define __WCXB_SPI_H
#include <linux/spi/spi.h>
#include <stdbool.h>
struct wcxb_spi_transfer {
const void *tx_buf;
void *rx_buf;
u32 len:16;
u16 delay_usecs;
struct list_head node;
};
struct wcxb_spi_message {
struct list_head transfers;
struct list_head node;
struct wcxb_spi_device *spi;
void (*complete)(void *arg);
void *arg;
int status;
};
struct wcxb_spi_master;
struct wcxb_spi_device {
struct wcxb_spi_master *master;
u16 chip_select;
};
extern struct wcxb_spi_master *wcxb_spi_master_create(struct device *parent,
void __iomem *base, bool auto_cs);
extern void wcxb_spi_master_destroy(struct wcxb_spi_master *master);
extern int wcxb_spi_sync(struct wcxb_spi_device *spi,
struct wcxb_spi_message *message);
extern int wcxb_spi_async(struct wcxb_spi_device *spi,
struct wcxb_spi_message *message);
extern void wcxb_spi_handle_interrupt(struct wcxb_spi_master *master);
static inline struct wcxb_spi_device *
wcxb_spi_device_create(struct wcxb_spi_master *master, u16 chip_select)
{
struct wcxb_spi_device *spi = kzalloc(sizeof(*spi), GFP_KERNEL);
if (!spi)
return NULL;
spi->master = master;
spi->chip_select = chip_select;
return spi;
}
static inline void wcxb_spi_device_destroy(struct wcxb_spi_device *spi)
{
kfree(spi);
}
static inline void wcxb_spi_message_init(struct wcxb_spi_message *m)
{
memset(m, 0, sizeof(*m));
INIT_LIST_HEAD(&m->transfers);
}
static inline void wcxb_spi_message_add_tail(struct wcxb_spi_transfer *t,
struct wcxb_spi_message *m)
{
list_add_tail(&t->node, &m->transfers);
}
static inline int
wcxb_spi_write(struct wcxb_spi_device *spi, const void *buffer, size_t len)
{
struct wcxb_spi_transfer t = {
.tx_buf = buffer,
.len = len,
};
struct wcxb_spi_message m;
wcxb_spi_message_init(&m);
wcxb_spi_message_add_tail(&t, &m);
return wcxb_spi_sync(spi, &m);
}
static inline int
wcxb_spi_read(struct wcxb_spi_device *spi, void *buffer, size_t len)
{
struct wcxb_spi_transfer t = {
.rx_buf = buffer,
.len = len,
};
struct wcxb_spi_message m;
wcxb_spi_message_init(&m);
wcxb_spi_message_add_tail(&t, &m);
return wcxb_spi_sync(spi, &m);
}
#endif