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:
parent
26efcf6902
commit
348f6ab030
@ -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
|
||||
|
@ -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
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
919
drivers/dahdi/wcxb.c
Normal 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
184
drivers/dahdi/wcxb.h
Normal 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
170
drivers/dahdi/wcxb_flash.c
Normal 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;
|
||||
}
|
34
drivers/dahdi/wcxb_flash.h
Normal file
34
drivers/dahdi/wcxb_flash.h
Normal 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
382
drivers/dahdi/wcxb_spi.c
Normal 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
116
drivers/dahdi/wcxb_spi.h
Normal 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
|
Loading…
Reference in New Issue
Block a user