summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/dahdi/firmware/Makefile15
-rw-r--r--drivers/dahdi/wct4xxp/base.c364
2 files changed, 374 insertions, 5 deletions
diff --git a/drivers/dahdi/firmware/Makefile b/drivers/dahdi/firmware/Makefile
index 4aeda07..2d10175 100644
--- a/drivers/dahdi/firmware/Makefile
+++ b/drivers/dahdi/firmware/Makefile
@@ -29,10 +29,11 @@ TC400M_VERSION:=MR6.12
VPMADT032_VERSION:=1.25.0
HX8_VERSION:=2.06
VPMOCT032_VERSION:=1.11.0
+WCT820_VERSION:=1.76
FIRMWARE_URL:=http://downloads.digium.com/pub/telephony/firmware/releases
-ALL_FIRMWARE=FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-OCT6114-256 FIRMWARE-TC400M FIRMWARE-HX8 FIRMWARE-VPMOCT032
+ALL_FIRMWARE=FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-OCT6114-256 FIRMWARE-TC400M FIRMWARE-HX8 FIRMWARE-VPMOCT032 FIRMWARE-TE820
# 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
@@ -47,6 +48,7 @@ FIRMWARE:=$(FIRMWARE:FIRMWARE-OCT6114-256=dahdi-fw-oct6114-256-$(OCT6114_256_VER
FIRMWARE:=$(FIRMWARE:FIRMWARE-TC400M=dahdi-fw-tc400m-$(TC400M_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-HX8=dahdi-fw-hx8-$(HX8_VERSION).tar.gz)
FIRMWARE:=$(FIRMWARE:FIRMWARE-VPMOCT032=dahdi-fw-vpmoct032-$(VPMOCT032_VERSION).tar.gz)
+FIRMWARE:=$(FIRMWARE:FIRMWARE-TE820=dahdi-fw-te820-$(WCT820_VERSION).tar.gz)
FWLOADERS:=dahdi-fwload-vpmadt032-$(VPMADT032_VERSION).tar.gz
@@ -170,6 +172,17 @@ ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-vpmoct032-
else
@echo "Firmware dahdi-fw-vpmoct032.bin is already installed with required version $(VPMOCT032_VERSION)"
endif
+ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-te820-$(WCT820_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-te820-$(WCT820_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes)
+ @echo "Installing dahdi-fw-te820.bin to hotplug firmware directories"
+ @install -m 644 dahdi-fw-te820.bin $(DESTDIR)/usr/lib/hotplug/firmware
+ @rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-te820-*
+ @touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-te820-$(WCT820_VERSION)
+ @install -m 644 dahdi-fw-te820.bin $(DESTDIR)/lib/firmware
+ @rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-te820-*
+ @touch $(DESTDIR)/lib/firmware/.dahdi-fw-te820-$(WCT820_VERSION)
+else
+ @echo "Firmware dahdi-fw-te820.bin is already installed with required version $(WCT820_VERSION)"
+endif
# Uninstall any installed dahdi firmware images from hotplug firmware directories
hotplug-uninstall:
diff --git a/drivers/dahdi/wct4xxp/base.c b/drivers/dahdi/wct4xxp/base.c
index 6326ed0..62c3415 100644
--- a/drivers/dahdi/wct4xxp/base.c
+++ b/drivers/dahdi/wct4xxp/base.c
@@ -39,6 +39,7 @@
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
+#include <linux/crc32.h>
#include <stdbool.h>
#include <dahdi/kernel.h>
@@ -273,6 +274,11 @@ struct t4;
enum linemode {T1, E1, J1};
+struct spi_state {
+ int wrreg;
+ int rdreg;
+};
+
struct t4_span {
struct t4 *owner;
u32 *writechunk; /* Double-word aligned write memory */
@@ -369,7 +375,7 @@ struct t4 {
#ifdef VPM_SUPPORT
struct vpm450m *vpm450m;
#endif
-
+ struct spi_state st;
};
static inline bool has_e1_span(const struct t4 *wc)
@@ -533,6 +539,12 @@ static void t4_isr_bh(unsigned long data);
static struct t4 *cards[MAX_T4_CARDS];
+struct t8_firm_header {
+ u8 header[6];
+ __le32 chksum;
+ u8 pad[18];
+ __le32 version;
+} __packed;
#define MAX_TDM_CHAN 32
#define MAX_DTMF_DET 16
@@ -4375,9 +4387,314 @@ static void t4_extended_reset(struct t4 *wc)
}
#endif
+#define SPI_CS (0)
+#define SPI_CLK (1)
+#define SPI_IO0 (2)
+#define SPI_IO1 (3)
+#define SPI_IO3 (4)
+#define SPI_IO2 (5)
+#define SPI_IN (0)
+#define SPI_OUT (1)
+#define ESPI_REG 13
+
+static void t8_clear_bit(struct t4 *wc, int whichb)
+{
+ wc->st.wrreg &= ~(1 << whichb);
+}
+
+static void t8_set_bit(struct t4 *wc, int whichb, int val)
+{
+ t8_clear_bit(wc, whichb);
+ wc->st.wrreg |= (val << whichb);
+}
+
+static int t8_get_bit(struct t4 *wc, int whichb)
+{
+ return (wc->st.rdreg >> whichb) & 1;
+}
+
+static void set_iodir(struct t4 *wc, int whichb, int dir)
+{
+ whichb += 16;
+ t8_clear_bit(wc, whichb);
+ t8_set_bit(wc, whichb, dir);
+}
+
+static void write_hwreg(struct t4 *wc)
+{
+ t4_pci_out(wc, ESPI_REG, wc->st.wrreg);
+}
+
+static void read_hwreg(struct t4 *wc)
+{
+ wc->st.rdreg = t4_pci_in(wc, ESPI_REG);
+}
+
+static void set_cs(struct t4 *wc, int state)
+{
+ t8_set_bit(wc, SPI_CS, state);
+ write_hwreg(wc);
+}
+
+static void set_clk(struct t4 *wc, int clk)
+{
+ t8_set_bit(wc, SPI_CLK, clk);
+ write_hwreg(wc);
+}
+
+static void clk_bit_out(struct t4 *wc, int val)
+{
+ t8_set_bit(wc, SPI_IO0, val & 1);
+ set_clk(wc, 0);
+ set_clk(wc, 1);
+}
+
+static void shift_out(struct t4 *wc, int val)
+{
+ int i;
+ for (i = 7; i >= 0; i--)
+ clk_bit_out(wc, (val >> i) & 1);
+}
+
+static int clk_bit_in(struct t4 *wc)
+{
+ int ret;
+ set_clk(wc, 0);
+ read_hwreg(wc);
+ ret = t8_get_bit(wc, SPI_IO1);
+ set_clk(wc, 1);
+ return ret;
+}
+
+static int shift_in(struct t4 *wc)
+{
+ int ret = 0;
+ int i;
+ int bit;
+
+ for (i = 7; i >= 0; i--) {
+ bit = clk_bit_in(wc);
+ ret |= ((bit & 1) << i);
+ }
+ return ret;
+}
+
+static void write_enable(struct t4 *wc)
+{
+ int cmd = 0x06;
+ set_cs(wc, 0);
+ shift_out(wc, cmd);
+ set_cs(wc, 1);
+}
+
+static int read_sr1(struct t4 *wc)
+{
+ int cmd = 0x05;
+ int ret;
+ set_cs(wc, 0);
+ shift_out(wc, cmd);
+ ret = shift_in(wc);
+ set_cs(wc, 1);
+ return ret;
+}
+
+static void clear_busy(struct t4 *wc)
+{
+ static const int SR1_BUSY = (1 << 0);
+ unsigned long stop;
+
+ stop = jiffies + 2*HZ;
+ while (read_sr1(wc) & SR1_BUSY) {
+ if (time_after(jiffies, stop)) {
+ if (printk_ratelimit()) {
+ dev_err(&wc->dev->dev,
+ "Lockup in %s\n", __func__);
+ }
+ break;
+ }
+ cond_resched();
+ }
+}
+
+static void sector_erase(struct t4 *wc, uint32_t addr)
+{
+ int cmd = 0x20;
+ write_enable(wc);
+ set_cs(wc, 0);
+ shift_out(wc, cmd);
+ shift_out(wc, (addr >> 16) & 0xff);
+ shift_out(wc, (addr >> 8) & 0xff);
+ shift_out(wc, (addr >> 0) & 0xff);
+ set_cs(wc, 1);
+ clear_busy(wc);
+}
+
+static void erase_half(struct t4 *wc)
+{
+ uint32_t addr = 0x00080000;
+ uint32_t i;
+
+ dev_info(&wc->dev->dev, "Erasing octal firmware\n");
+
+ for (i = addr; i < (addr + 0x80000); i += 4096)
+ sector_erase(wc, i);
+}
+
+
+#define T8_FLASH_PAGE_SIZE 256UL
+
+static void t8_update_firmware_page(struct t4 *wc, u32 address,
+ const u8 *page_data, size_t size)
+{
+ int i;
+
+ write_enable(wc);
+ set_cs(wc, 0);
+ shift_out(wc, 0x02);
+ shift_out(wc, (address >> 16) & 0xff);
+ shift_out(wc, (address >> 8) & 0xff);
+ shift_out(wc, (address >> 0) & 0xff);
+
+ for (i = 0; i < size; ++i)
+ shift_out(wc, page_data[i]);
+
+ set_cs(wc, 1);
+ clear_busy(wc);
+}
+
+static int t8_update_firmware(struct t4 *wc, const struct firmware *fw,
+ const char *t8_firmware)
+{
+ int res;
+ size_t offset = 0;
+ const u32 BASE_ADDRESS = 0x00080000;
+ const u8 *data, *end;
+ size_t size = 0;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+ u32 *pci_state;
+ pci_state = kzalloc(64 * sizeof(u32), GFP_KERNEL);
+ if (!pci_state)
+ return -ENOMEM;
+#endif
+
+ /* Erase flash */
+ erase_half(wc);
+
+ dev_info(&wc->dev->dev,
+ "Uploading %s. This can take up to 30 seconds.\n", t8_firmware);
+
+ data = &fw->data[sizeof(struct t8_firm_header)];
+ end = &fw->data[fw->size];
+
+ while (data < end) {
+ /* Calculate the tail end of data that's shorter than a page */
+ size = min(T8_FLASH_PAGE_SIZE, (unsigned long)(end - data));
+
+ t8_update_firmware_page(wc, BASE_ADDRESS + offset,
+ data, size);
+ data += T8_FLASH_PAGE_SIZE;
+ offset += T8_FLASH_PAGE_SIZE;
+
+ cond_resched();
+ }
+
+ /* Reset te820 fpga after loading firmware */
+ dev_info(&wc->dev->dev, "Firmware load complete. Reseting device.\n");
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+ res = pci_save_state(wc->dev, pci_state);
+#else
+ res = pci_save_state(wc->dev);
+#endif
+ if (res)
+ goto error_exit;
+ /* Set the fpga reset bits and clobber the remainder of the
+ * register, device will be reset anyway */
+ t4_pci_out(wc, WC_LEDS, 0xe0000000);
+ msleep(1000);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+ pci_restore_state(wc->dev, pci_state);
+#else
+ pci_restore_state(wc->dev);
+#endif
+
+ /* Signal the driver to restart initialization.
+ * This will back out all initialization so far and
+ * restart the driver load process */
+ return -EAGAIN;
+
+error_exit:
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10)
+ kfree(pci_state);
+#endif
+ return res;
+}
+
+static void setup_spi(struct t4 *wc)
+{
+ wc->st.rdreg = wc->st.wrreg = 0;
+
+ set_iodir(wc, SPI_IO0, SPI_OUT);
+ set_iodir(wc, SPI_IO1, SPI_IN);
+ set_iodir(wc, SPI_CS, SPI_OUT);
+ set_iodir(wc, SPI_CLK, SPI_OUT);
+
+ t8_set_bit(wc, SPI_CS, 1);
+ t8_set_bit(wc, SPI_CLK, 1);
+
+ write_hwreg(wc);
+}
+
+static int t8_check_firmware(struct t4 *wc, unsigned int version)
+{
+ const struct firmware *fw;
+ static const char t8_firmware[] = "dahdi-fw-te820.bin";
+ const struct t8_firm_header *header;
+ int res = 0;
+ u32 crc;
+
+ res = request_firmware(&fw, t8_firmware, &wc->dev->dev);
+ if (res) {
+ dev_info(&wc->dev->dev, "firmware %s not "
+ "available from userspace\n", t8_firmware);
+ goto cleanup;
+ }
+
+ header = (const struct t8_firm_header *)fw->data;
+
+ /* Check the crc before anything else */
+ 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(&wc->dev->dev,
+ "%s is invalid. Please reinstall.\n", t8_firmware);
+ goto cleanup;
+ }
+
+ /* Check the two firmware versions */
+ if (le32_to_cpu(header->version) == version)
+ goto cleanup;
+
+ dev_info(&wc->dev->dev, "%s Version: %08x available for flash\n",
+ t8_firmware, header->version);
+
+ setup_spi(wc);
+
+ res = t8_update_firmware(wc, fw, t8_firmware);
+ if (res && res != -EAGAIN) {
+ dev_info(&wc->dev->dev, "Failed to load firmware %s\n",
+ t8_firmware);
+ }
+
+cleanup:
+ release_firmware(fw);
+ return res;
+}
+
static int t4_hardware_init_1(struct t4 *wc, unsigned int cardflags)
{
unsigned int version;
+ int res;
version = t4_pci_in(wc, WC_VERSION);
if (is_octal(wc)) {
@@ -4395,6 +4712,13 @@ static int t4_hardware_init_1(struct t4 *wc, unsigned int cardflags)
#endif
}
+ /* Check the field updatable firmware for the wcte820 */
+ if (is_octal(wc)) {
+ res = t8_check_firmware(wc, version);
+ if (res)
+ return res;
+ }
+
#if defined(CONFIG_FORCE_EXTENDED_RESET)
t4_extended_reset(wc);
#elif !defined(CONFIG_NOEXTENDED_RESET)
@@ -4615,7 +4939,8 @@ static void wct4xxp_sort_cards(void)
}
}
-static int __devinit t4_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+static int __devinit
+t4_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
int res;
struct t4 *wc;
@@ -4692,7 +5017,19 @@ static int __devinit t4_init_one(struct pci_dev *pdev, const struct pci_device_i
}
/* Initialize hardware */
- t4_hardware_init_1(wc, wc->devtype->flags);
+ res = t4_hardware_init_1(wc, wc->devtype->flags);
+ if (res) {
+ /* If this function returns -EAGAIN, we expect
+ * to attempt another driver load. Clean everything
+ * up first */
+ pci_iounmap(wc->dev, wc->membase);
+ pci_release_regions(wc->dev);
+ pci_free_consistent(wc->dev, T4_BASE_SIZE(wc) * wc->numbufs * 2,
+ wc->writechunk, wc->writedma);
+ pci_set_drvdata(wc->dev, NULL);
+ free_wc(wc);
+ return res;
+ }
for(x = 0; x < MAX_T4_CARDS; x++) {
if (!cards[x])
@@ -4822,6 +5159,25 @@ static int t4_hardware_stop(struct t4 *wc)
return 0;
}
+static int __devinit
+t4_init_one_retry(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int res;
+ res = t4_init_one(pdev, ent);
+
+ /* If the driver was reset by a firmware load,
+ * try to load once again */
+ if (-EAGAIN == res) {
+ res = t4_init_one(pdev, ent);
+ if (-EAGAIN == res) {
+ dev_err(&pdev->dev, "Failed to update firmware.\n");
+ res = -EIO;
+ }
+ }
+
+ return res;
+}
+
static void _t4_remove_one(struct t4 *wc)
{
int basesize;
@@ -4927,7 +5283,7 @@ static int t4_suspend(struct pci_dev *pdev, pm_message_t state)
static struct pci_driver t4_driver = {
.name = "wct4xxp",
- .probe = t4_init_one,
+ .probe = t4_init_one_retry,
.remove = __devexit_p(t4_remove_one),
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 12)
.shutdown = _t4_shutdown,