Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / soc / codecs / wm0010.c
diff --git a/kernel/sound/soc/codecs/wm0010.c b/kernel/sound/soc/codecs/wm0010.c
new file mode 100644 (file)
index 0000000..f37989e
--- /dev/null
@@ -0,0 +1,1019 @@
+/*
+ * wm0010.c  --  WM0010 DSP Driver
+ *
+ * Copyright 2012 Wolfson Microelectronics PLC.
+ *
+ * Authors: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *          Dimitris Papastamos <dp@opensource.wolfsonmicro.com>
+ *          Scott Ling <sl@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/init.h>
+#include <linux/spi/spi.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#include <sound/soc.h>
+#include <sound/wm0010.h>
+
+#define DEVICE_ID_WM0010       10
+
+/* We only support v1 of the .dfw INFO record */
+#define INFO_VERSION           1
+
+enum dfw_cmd {
+       DFW_CMD_FUSE = 0x01,
+       DFW_CMD_CODE_HDR,
+       DFW_CMD_CODE_DATA,
+       DFW_CMD_PLL,
+       DFW_CMD_INFO = 0xff
+};
+
+struct dfw_binrec {
+       u8 command;
+       u32 length:24;
+       u32 address;
+       uint8_t data[0];
+} __packed;
+
+struct dfw_inforec {
+       u8 info_version;
+       u8 tool_major_version;
+       u8 tool_minor_version;
+       u8 dsp_target;
+};
+
+struct dfw_pllrec {
+       u8 command;
+       u32 length:24;
+       u32 address;
+       u32 clkctrl1;
+       u32 clkctrl2;
+       u32 clkctrl3;
+       u32 ldetctrl;
+       u32 uart_div;
+       u32 spi_div;
+} __packed;
+
+static struct pll_clock_map {
+       int max_sysclk;
+       int max_pll_spi_speed;
+       u32 pll_clkctrl1;
+} pll_clock_map[] = {                     /* Dividers */
+       { 22000000, 26000000, 0x00201f11 }, /* 2,32,2  */
+       { 18000000, 26000000, 0x00203f21 }, /* 2,64,4  */
+       { 14000000, 26000000, 0x00202620 }, /* 1,39,4  */
+       { 10000000, 22000000, 0x00203120 }, /* 1,50,4  */
+       {  6500000, 22000000, 0x00204520 }, /* 1,70,4  */
+       {  5500000, 22000000, 0x00103f10 }, /* 1,64,2  */
+};
+
+enum wm0010_state {
+       WM0010_POWER_OFF,
+       WM0010_OUT_OF_RESET,
+       WM0010_BOOTROM,
+       WM0010_STAGE2,
+       WM0010_FIRMWARE,
+};
+
+struct wm0010_priv {
+       struct snd_soc_codec *codec;
+
+       struct mutex lock;
+       struct device *dev;
+
+       struct wm0010_pdata pdata;
+
+       int gpio_reset;
+       int gpio_reset_value;
+
+       struct regulator_bulk_data core_supplies[2];
+       struct regulator *dbvdd;
+
+       int sysclk;
+
+       enum wm0010_state state;
+       bool boot_failed;
+       bool ready;
+       bool pll_running;
+       int max_spi_freq;
+       int board_max_spi_speed;
+       u32 pll_clkctrl1;
+
+       spinlock_t irq_lock;
+       int irq;
+
+       struct completion boot_completion;
+};
+
+struct wm0010_spi_msg {
+       struct spi_message m;
+       struct spi_transfer t;
+       u8 *tx_buf;
+       u8 *rx_buf;
+       size_t len;
+};
+
+static const struct snd_soc_dapm_widget wm0010_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("CLKIN",  SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route wm0010_dapm_routes[] = {
+       { "SDI2 Capture", NULL, "SDI1 Playback" },
+       { "SDI1 Capture", NULL, "SDI2 Playback" },
+
+       { "SDI1 Capture", NULL, "CLKIN" },
+       { "SDI2 Capture", NULL, "CLKIN" },
+       { "SDI1 Playback", NULL, "CLKIN" },
+       { "SDI2 Playback", NULL, "CLKIN" },
+};
+
+static const char *wm0010_state_to_str(enum wm0010_state state)
+{
+       static const char * const state_to_str[] = {
+               "Power off",
+               "Out of reset",
+               "Boot ROM",
+               "Stage2",
+               "Firmware"
+       };
+
+       if (state < 0 || state >= ARRAY_SIZE(state_to_str))
+               return "null";
+       return state_to_str[state];
+}
+
+/* Called with wm0010->lock held */
+static void wm0010_halt(struct snd_soc_codec *codec)
+{
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       unsigned long flags;
+       enum wm0010_state state;
+
+       /* Fetch the wm0010 state */
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       state = wm0010->state;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       switch (state) {
+       case WM0010_POWER_OFF:
+               /* If there's nothing to do, bail out */
+               return;
+       case WM0010_OUT_OF_RESET:
+       case WM0010_BOOTROM:
+       case WM0010_STAGE2:
+       case WM0010_FIRMWARE:
+               /* Remember to put chip back into reset */
+               gpio_set_value_cansleep(wm0010->gpio_reset,
+                                       wm0010->gpio_reset_value);
+               /* Disable the regulators */
+               regulator_disable(wm0010->dbvdd);
+               regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies),
+                                      wm0010->core_supplies);
+               break;
+       }
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       wm0010->state = WM0010_POWER_OFF;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+}
+
+struct wm0010_boot_xfer {
+       struct list_head list;
+       struct snd_soc_codec *codec;
+       struct completion *done;
+       struct spi_message m;
+       struct spi_transfer t;
+};
+
+/* Called with wm0010->lock held */
+static void wm0010_mark_boot_failure(struct wm0010_priv *wm0010)
+{
+       enum wm0010_state state;
+       unsigned long flags;
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       state = wm0010->state;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       dev_err(wm0010->dev, "Failed to transition from `%s' state to `%s' state\n",
+               wm0010_state_to_str(state), wm0010_state_to_str(state + 1));
+
+       wm0010->boot_failed = true;
+}
+
+static void wm0010_boot_xfer_complete(void *data)
+{
+       struct wm0010_boot_xfer *xfer = data;
+       struct snd_soc_codec *codec = xfer->codec;
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       u32 *out32 = xfer->t.rx_buf;
+       int i;
+
+       if (xfer->m.status != 0) {
+               dev_err(codec->dev, "SPI transfer failed: %d\n",
+                       xfer->m.status);
+               wm0010_mark_boot_failure(wm0010);
+               if (xfer->done)
+                       complete(xfer->done);
+               return;
+       }
+
+       for (i = 0; i < xfer->t.len / 4; i++) {
+               dev_dbg(codec->dev, "%d: %04x\n", i, out32[i]);
+
+               switch (be32_to_cpu(out32[i])) {
+               case 0xe0e0e0e0:
+                       dev_err(codec->dev,
+                               "%d: ROM error reported in stage 2\n", i);
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x55555555:
+                       if (wm0010->state < WM0010_STAGE2)
+                               break;
+                       dev_err(codec->dev,
+                               "%d: ROM bootloader running in stage 2\n", i);
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0000:
+                       dev_dbg(codec->dev, "Stage2 loader running\n");
+                       break;
+
+               case 0x0fed0007:
+                       dev_dbg(codec->dev, "CODE_HDR packet received\n");
+                       break;
+
+               case 0x0fed0008:
+                       dev_dbg(codec->dev, "CODE_DATA packet received\n");
+                       break;
+
+               case 0x0fed0009:
+                       dev_dbg(codec->dev, "Download complete\n");
+                       break;
+
+               case 0x0fed000c:
+                       dev_dbg(codec->dev, "Application start\n");
+                       break;
+
+               case 0x0fed000e:
+                       dev_dbg(codec->dev, "PLL packet received\n");
+                       wm0010->pll_running = true;
+                       break;
+
+               case 0x0fed0025:
+                       dev_err(codec->dev, "Device reports image too long\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed002c:
+                       dev_err(codec->dev, "Device reports bad SPI packet\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0031:
+                       dev_err(codec->dev, "Device reports SPI read overflow\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0032:
+                       dev_err(codec->dev, "Device reports SPI underclock\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0033:
+                       dev_err(codec->dev, "Device reports bad header packet\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0034:
+                       dev_err(codec->dev, "Device reports invalid packet type\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0035:
+                       dev_err(codec->dev, "Device reports data before header error\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               case 0x0fed0038:
+                       dev_err(codec->dev, "Device reports invalid PLL packet\n");
+                       break;
+
+               case 0x0fed003a:
+                       dev_err(codec->dev, "Device reports packet alignment error\n");
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+
+               default:
+                       dev_err(codec->dev, "Unrecognised return 0x%x\n",
+                           be32_to_cpu(out32[i]));
+                       wm0010_mark_boot_failure(wm0010);
+                       break;
+               }
+
+               if (wm0010->boot_failed)
+                       break;
+       }
+
+       if (xfer->done)
+               complete(xfer->done);
+}
+
+static void byte_swap_64(u64 *data_in, u64 *data_out, u32 len)
+{
+       int i;
+
+       for (i = 0; i < len / 8; i++)
+               data_out[i] = cpu_to_be64(le64_to_cpu(data_in[i]));
+}
+
+static int wm0010_firmware_load(const char *name, struct snd_soc_codec *codec)
+{
+       struct spi_device *spi = to_spi_device(codec->dev);
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       struct list_head xfer_list;
+       struct wm0010_boot_xfer *xfer;
+       int ret;
+       struct completion done;
+       const struct firmware *fw;
+       const struct dfw_binrec *rec;
+       const struct dfw_inforec *inforec;
+       u64 *img;
+       u8 *out, dsp;
+       u32 len, offset;
+
+       INIT_LIST_HEAD(&xfer_list);
+
+       ret = request_firmware(&fw, name, codec->dev);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to request application(%s): %d\n",
+                       name, ret);
+               return ret;
+       }
+
+       rec = (const struct dfw_binrec *)fw->data;
+       inforec = (const struct dfw_inforec *)rec->data;
+       offset = 0;
+       dsp = inforec->dsp_target;
+       wm0010->boot_failed = false;
+       if (WARN_ON(!list_empty(&xfer_list)))
+               return -EINVAL;
+       init_completion(&done);
+
+       /* First record should be INFO */
+       if (rec->command != DFW_CMD_INFO) {
+               dev_err(codec->dev, "First record not INFO\r\n");
+               ret = -EINVAL;
+               goto abort;
+       }
+
+       if (inforec->info_version != INFO_VERSION) {
+               dev_err(codec->dev,
+                       "Unsupported version (%02d) of INFO record\r\n",
+                       inforec->info_version);
+               ret = -EINVAL;
+               goto abort;
+       }
+
+       dev_dbg(codec->dev, "Version v%02d INFO record found\r\n",
+               inforec->info_version);
+
+       /* Check it's a DSP file */
+       if (dsp != DEVICE_ID_WM0010) {
+               dev_err(codec->dev, "Not a WM0010 firmware file.\r\n");
+               ret = -EINVAL;
+               goto abort;
+       }
+
+       /* Skip the info record as we don't need to send it */
+       offset += ((rec->length) + 8);
+       rec = (void *)&rec->data[rec->length];
+
+       while (offset < fw->size) {
+               dev_dbg(codec->dev,
+                       "Packet: command %d, data length = 0x%x\r\n",
+                       rec->command, rec->length);
+               len = rec->length + 8;
+
+               xfer = kzalloc(sizeof(*xfer), GFP_KERNEL);
+               if (!xfer) {
+                       ret = -ENOMEM;
+                       goto abort;
+               }
+
+               xfer->codec = codec;
+               list_add_tail(&xfer->list, &xfer_list);
+
+               out = kzalloc(len, GFP_KERNEL | GFP_DMA);
+               if (!out) {
+                       ret = -ENOMEM;
+                       goto abort1;
+               }
+               xfer->t.rx_buf = out;
+
+               img = kzalloc(len, GFP_KERNEL | GFP_DMA);
+               if (!img) {
+                       ret = -ENOMEM;
+                       goto abort1;
+               }
+               xfer->t.tx_buf = img;
+
+               byte_swap_64((u64 *)&rec->command, img, len);
+
+               spi_message_init(&xfer->m);
+               xfer->m.complete = wm0010_boot_xfer_complete;
+               xfer->m.context = xfer;
+               xfer->t.len = len;
+               xfer->t.bits_per_word = 8;
+
+               if (!wm0010->pll_running) {
+                       xfer->t.speed_hz = wm0010->sysclk / 6;
+               } else {
+                       xfer->t.speed_hz = wm0010->max_spi_freq;
+
+                       if (wm0010->board_max_spi_speed &&
+                          (wm0010->board_max_spi_speed < wm0010->max_spi_freq))
+                                       xfer->t.speed_hz = wm0010->board_max_spi_speed;
+               }
+
+               /* Store max usable spi frequency for later use */
+               wm0010->max_spi_freq = xfer->t.speed_hz;
+
+               spi_message_add_tail(&xfer->t, &xfer->m);
+
+               offset += ((rec->length) + 8);
+               rec = (void *)&rec->data[rec->length];
+
+               if (offset >= fw->size) {
+                       dev_dbg(codec->dev, "All transfers scheduled\n");
+                       xfer->done = &done;
+               }
+
+               ret = spi_async(spi, &xfer->m);
+               if (ret != 0) {
+                       dev_err(codec->dev, "Write failed: %d\n", ret);
+                       goto abort1;
+               }
+
+               if (wm0010->boot_failed) {
+                       dev_dbg(codec->dev, "Boot fail!\n");
+                       ret = -EINVAL;
+                       goto abort1;
+               }
+       }
+
+       wait_for_completion(&done);
+
+       ret = 0;
+
+abort1:
+       while (!list_empty(&xfer_list)) {
+               xfer = list_first_entry(&xfer_list, struct wm0010_boot_xfer,
+                                       list);
+               kfree(xfer->t.rx_buf);
+               kfree(xfer->t.tx_buf);
+               list_del(&xfer->list);
+               kfree(xfer);
+       }
+
+abort:
+       release_firmware(fw);
+       return ret;
+}
+
+static int wm0010_stage2_load(struct snd_soc_codec *codec)
+{
+       struct spi_device *spi = to_spi_device(codec->dev);
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       const struct firmware *fw;
+       struct spi_message m;
+       struct spi_transfer t;
+       u32 *img;
+       u8 *out;
+       int i;
+       int ret = 0;
+
+       ret = request_firmware(&fw, "wm0010_stage2.bin", codec->dev);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to request stage2 loader: %d\n",
+                       ret);
+               return ret;
+       }
+
+       dev_dbg(codec->dev, "Downloading %zu byte stage 2 loader\n", fw->size);
+
+       /* Copy to local buffer first as vmalloc causes problems for dma */
+       img = kzalloc(fw->size, GFP_KERNEL | GFP_DMA);
+       if (!img) {
+               ret = -ENOMEM;
+               goto abort2;
+       }
+
+       out = kzalloc(fw->size, GFP_KERNEL | GFP_DMA);
+       if (!out) {
+               ret = -ENOMEM;
+               goto abort1;
+       }
+
+       memcpy(img, &fw->data[0], fw->size);
+
+       spi_message_init(&m);
+       memset(&t, 0, sizeof(t));
+       t.rx_buf = out;
+       t.tx_buf = img;
+       t.len = fw->size;
+       t.bits_per_word = 8;
+       t.speed_hz = wm0010->sysclk / 10;
+       spi_message_add_tail(&t, &m);
+
+       dev_dbg(codec->dev, "Starting initial download at %dHz\n",
+               t.speed_hz);
+
+       ret = spi_sync(spi, &m);
+       if (ret != 0) {
+               dev_err(codec->dev, "Initial download failed: %d\n", ret);
+               goto abort;
+       }
+
+       /* Look for errors from the boot ROM */
+       for (i = 0; i < fw->size; i++) {
+               if (out[i] != 0x55) {
+                       dev_err(codec->dev, "Boot ROM error: %x in %d\n",
+                               out[i], i);
+                       wm0010_mark_boot_failure(wm0010);
+                       ret = -EBUSY;
+                       goto abort;
+               }
+       }
+abort:
+       kfree(out);
+abort1:
+       kfree(img);
+abort2:
+       release_firmware(fw);
+
+       return ret;
+}
+
+static int wm0010_boot(struct snd_soc_codec *codec)
+{
+       struct spi_device *spi = to_spi_device(codec->dev);
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       unsigned long flags;
+       int ret;
+       const struct firmware *fw;
+       struct spi_message m;
+       struct spi_transfer t;
+       struct dfw_pllrec pll_rec;
+       u32 *p, len;
+       u64 *img_swap;
+       u8 *out;
+       int i;
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       if (wm0010->state != WM0010_POWER_OFF)
+               dev_warn(wm0010->dev, "DSP already powered up!\n");
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       if (wm0010->sysclk > 26000000) {
+               dev_err(codec->dev, "Max DSP clock frequency is 26MHz\n");
+               ret = -ECANCELED;
+               goto err;
+       }
+
+       mutex_lock(&wm0010->lock);
+       wm0010->pll_running = false;
+
+       dev_dbg(codec->dev, "max_spi_freq: %d\n", wm0010->max_spi_freq);
+
+       ret = regulator_bulk_enable(ARRAY_SIZE(wm0010->core_supplies),
+                                   wm0010->core_supplies);
+       if (ret != 0) {
+               dev_err(&spi->dev, "Failed to enable core supplies: %d\n",
+                       ret);
+               mutex_unlock(&wm0010->lock);
+               goto err;
+       }
+
+       ret = regulator_enable(wm0010->dbvdd);
+       if (ret != 0) {
+               dev_err(&spi->dev, "Failed to enable DBVDD: %d\n", ret);
+               goto err_core;
+       }
+
+       /* Release reset */
+       gpio_set_value_cansleep(wm0010->gpio_reset, !wm0010->gpio_reset_value);
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       wm0010->state = WM0010_OUT_OF_RESET;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       /* First the bootloader */
+       ret = request_firmware(&fw, "wm0010_stage2.bin", codec->dev);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to request stage2 loader: %d\n",
+                       ret);
+               goto abort;
+       }
+
+       if (!wait_for_completion_timeout(&wm0010->boot_completion,
+                                        msecs_to_jiffies(20)))
+               dev_err(codec->dev, "Failed to get interrupt from DSP\n");
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       wm0010->state = WM0010_BOOTROM;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       ret = wm0010_stage2_load(codec);
+       if (ret)
+               goto abort;
+
+       if (!wait_for_completion_timeout(&wm0010->boot_completion,
+                                        msecs_to_jiffies(20)))
+               dev_err(codec->dev, "Failed to get interrupt from DSP loader.\n");
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       wm0010->state = WM0010_STAGE2;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       /* Only initialise PLL if max_spi_freq initialised */
+       if (wm0010->max_spi_freq) {
+
+               /* Initialise a PLL record */
+               memset(&pll_rec, 0, sizeof(pll_rec));
+               pll_rec.command = DFW_CMD_PLL;
+               pll_rec.length = (sizeof(pll_rec) - 8);
+
+               /* On wm0010 only the CLKCTRL1 value is used */
+               pll_rec.clkctrl1 = wm0010->pll_clkctrl1;
+
+               ret = -ENOMEM;
+               len = pll_rec.length + 8;
+               out = kzalloc(len, GFP_KERNEL | GFP_DMA);
+               if (!out) {
+                       dev_err(codec->dev,
+                               "Failed to allocate RX buffer\n");
+                       goto abort;
+               }
+
+               img_swap = kzalloc(len, GFP_KERNEL | GFP_DMA);
+               if (!img_swap)
+                       goto abort;
+
+               /* We need to re-order for 0010 */
+               byte_swap_64((u64 *)&pll_rec, img_swap, len);
+
+               spi_message_init(&m);
+               memset(&t, 0, sizeof(t));
+               t.rx_buf = out;
+               t.tx_buf = img_swap;
+               t.len = len;
+               t.bits_per_word = 8;
+               t.speed_hz = wm0010->sysclk / 6;
+               spi_message_add_tail(&t, &m);
+
+               ret = spi_sync(spi, &m);
+               if (ret != 0) {
+                       dev_err(codec->dev, "First PLL write failed: %d\n", ret);
+                       goto abort;
+               }
+
+               /* Use a second send of the message to get the return status */
+               ret = spi_sync(spi, &m);
+               if (ret != 0) {
+                       dev_err(codec->dev, "Second PLL write failed: %d\n", ret);
+                       goto abort;
+               }
+
+               p = (u32 *)out;
+
+               /* Look for PLL active code from the DSP */
+               for (i = 0; i < len / 4; i++) {
+                       if (*p == 0x0e00ed0f) {
+                               dev_dbg(codec->dev, "PLL packet received\n");
+                               wm0010->pll_running = true;
+                               break;
+                       }
+                       p++;
+               }
+
+               kfree(img_swap);
+               kfree(out);
+       } else
+               dev_dbg(codec->dev, "Not enabling DSP PLL.");
+
+       ret = wm0010_firmware_load("wm0010.dfw", codec);
+
+       if (ret != 0)
+               goto abort;
+
+       spin_lock_irqsave(&wm0010->irq_lock, flags);
+       wm0010->state = WM0010_FIRMWARE;
+       spin_unlock_irqrestore(&wm0010->irq_lock, flags);
+
+       mutex_unlock(&wm0010->lock);
+
+       return 0;
+
+abort:
+       /* Put the chip back into reset */
+       wm0010_halt(codec);
+       mutex_unlock(&wm0010->lock);
+       return ret;
+
+err_core:
+       mutex_unlock(&wm0010->lock);
+       regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies),
+                              wm0010->core_supplies);
+err:
+       return ret;
+}
+
+static int wm0010_set_bias_level(struct snd_soc_codec *codec,
+                                enum snd_soc_bias_level level)
+{
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE)
+                       wm0010_boot(codec);
+               break;
+       case SND_SOC_BIAS_PREPARE:
+               break;
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) {
+                       mutex_lock(&wm0010->lock);
+                       wm0010_halt(codec);
+                       mutex_unlock(&wm0010->lock);
+               }
+               break;
+       case SND_SOC_BIAS_OFF:
+               break;
+       }
+
+       codec->dapm.bias_level = level;
+
+       return 0;
+}
+
+static int wm0010_set_sysclk(struct snd_soc_codec *codec, int source,
+                            int clk_id, unsigned int freq, int dir)
+{
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+       unsigned int i;
+
+       wm0010->sysclk = freq;
+
+       if (freq < pll_clock_map[ARRAY_SIZE(pll_clock_map)-1].max_sysclk) {
+               wm0010->max_spi_freq = 0;
+       } else {
+               for (i = 0; i < ARRAY_SIZE(pll_clock_map); i++)
+                       if (freq >= pll_clock_map[i].max_sysclk) {
+                               wm0010->max_spi_freq = pll_clock_map[i].max_pll_spi_speed;
+                               wm0010->pll_clkctrl1 = pll_clock_map[i].pll_clkctrl1;
+                               break;
+                       }
+       }
+
+       return 0;
+}
+
+static int wm0010_probe(struct snd_soc_codec *codec);
+
+static struct snd_soc_codec_driver soc_codec_dev_wm0010 = {
+       .probe = wm0010_probe,
+       .set_bias_level = wm0010_set_bias_level,
+       .set_sysclk = wm0010_set_sysclk,
+       .idle_bias_off = true,
+
+       .dapm_widgets = wm0010_dapm_widgets,
+       .num_dapm_widgets = ARRAY_SIZE(wm0010_dapm_widgets),
+       .dapm_routes = wm0010_dapm_routes,
+       .num_dapm_routes = ARRAY_SIZE(wm0010_dapm_routes),
+};
+
+#define WM0010_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define WM0010_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
+                       SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\
+                       SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver wm0010_dai[] = {
+       {
+               .name = "wm0010-sdi1",
+               .playback = {
+                       .stream_name = "SDI1 Playback",
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rates = WM0010_RATES,
+                       .formats = WM0010_FORMATS,
+               },
+               .capture = {
+                        .stream_name = "SDI1 Capture",
+                        .channels_min = 1,
+                        .channels_max = 2,
+                        .rates = WM0010_RATES,
+                        .formats = WM0010_FORMATS,
+                },
+       },
+       {
+               .name = "wm0010-sdi2",
+               .playback = {
+                       .stream_name = "SDI2 Playback",
+                       .channels_min = 1,
+                       .channels_max = 2,
+                       .rates = WM0010_RATES,
+                       .formats = WM0010_FORMATS,
+               },
+               .capture = {
+                        .stream_name = "SDI2 Capture",
+                        .channels_min = 1,
+                        .channels_max = 2,
+                        .rates = WM0010_RATES,
+                        .formats = WM0010_FORMATS,
+                },
+       },
+};
+
+static irqreturn_t wm0010_irq(int irq, void *data)
+{
+       struct wm0010_priv *wm0010 = data;
+
+       switch (wm0010->state) {
+       case WM0010_OUT_OF_RESET:
+       case WM0010_BOOTROM:
+       case WM0010_STAGE2:
+               spin_lock(&wm0010->irq_lock);
+               complete(&wm0010->boot_completion);
+               spin_unlock(&wm0010->irq_lock);
+               return IRQ_HANDLED;
+       default:
+               return IRQ_NONE;
+       }
+
+       return IRQ_NONE;
+}
+
+static int wm0010_probe(struct snd_soc_codec *codec)
+{
+       struct wm0010_priv *wm0010 = snd_soc_codec_get_drvdata(codec);
+
+       wm0010->codec = codec;
+
+       return 0;
+}
+
+static int wm0010_spi_probe(struct spi_device *spi)
+{
+       unsigned long gpio_flags;
+       int ret;
+       int trigger;
+       int irq;
+       struct wm0010_priv *wm0010;
+
+       wm0010 = devm_kzalloc(&spi->dev, sizeof(*wm0010),
+                             GFP_KERNEL);
+       if (!wm0010)
+               return -ENOMEM;
+
+       mutex_init(&wm0010->lock);
+       spin_lock_init(&wm0010->irq_lock);
+
+       spi_set_drvdata(spi, wm0010);
+       wm0010->dev = &spi->dev;
+
+       if (dev_get_platdata(&spi->dev))
+               memcpy(&wm0010->pdata, dev_get_platdata(&spi->dev),
+                      sizeof(wm0010->pdata));
+
+       init_completion(&wm0010->boot_completion);
+
+       wm0010->core_supplies[0].supply = "AVDD";
+       wm0010->core_supplies[1].supply = "DCVDD";
+       ret = devm_regulator_bulk_get(wm0010->dev, ARRAY_SIZE(wm0010->core_supplies),
+                                     wm0010->core_supplies);
+       if (ret != 0) {
+               dev_err(wm0010->dev, "Failed to obtain core supplies: %d\n",
+                       ret);
+               return ret;
+       }
+
+       wm0010->dbvdd = devm_regulator_get(wm0010->dev, "DBVDD");
+       if (IS_ERR(wm0010->dbvdd)) {
+               ret = PTR_ERR(wm0010->dbvdd);
+               dev_err(wm0010->dev, "Failed to obtain DBVDD: %d\n", ret);
+               return ret;
+       }
+
+       if (wm0010->pdata.gpio_reset) {
+               wm0010->gpio_reset = wm0010->pdata.gpio_reset;
+
+               if (wm0010->pdata.reset_active_high)
+                       wm0010->gpio_reset_value = 1;
+               else
+                       wm0010->gpio_reset_value = 0;
+
+               if (wm0010->gpio_reset_value)
+                       gpio_flags = GPIOF_OUT_INIT_HIGH;
+               else
+                       gpio_flags = GPIOF_OUT_INIT_LOW;
+
+               ret = devm_gpio_request_one(wm0010->dev, wm0010->gpio_reset,
+                                           gpio_flags, "wm0010 reset");
+               if (ret < 0) {
+                       dev_err(wm0010->dev,
+                               "Failed to request GPIO for DSP reset: %d\n",
+                               ret);
+                       return ret;
+               }
+       } else {
+               dev_err(wm0010->dev, "No reset GPIO configured\n");
+               return -EINVAL;
+       }
+
+       wm0010->state = WM0010_POWER_OFF;
+
+       irq = spi->irq;
+       if (wm0010->pdata.irq_flags)
+               trigger = wm0010->pdata.irq_flags;
+       else
+               trigger = IRQF_TRIGGER_FALLING;
+       trigger |= IRQF_ONESHOT;
+
+       ret = request_threaded_irq(irq, NULL, wm0010_irq, trigger | IRQF_ONESHOT,
+                                  "wm0010", wm0010);
+       if (ret) {
+               dev_err(wm0010->dev, "Failed to request IRQ %d: %d\n",
+                       irq, ret);
+               return ret;
+       }
+       wm0010->irq = irq;
+
+       ret = irq_set_irq_wake(irq, 1);
+       if (ret) {
+               dev_err(wm0010->dev, "Failed to set IRQ %d as wake source: %d\n",
+                       irq, ret);
+               return ret;
+       }
+
+       if (spi->max_speed_hz)
+               wm0010->board_max_spi_speed = spi->max_speed_hz;
+       else
+               wm0010->board_max_spi_speed = 0;
+
+       ret = snd_soc_register_codec(&spi->dev,
+                                    &soc_codec_dev_wm0010, wm0010_dai,
+                                    ARRAY_SIZE(wm0010_dai));
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int wm0010_spi_remove(struct spi_device *spi)
+{
+       struct wm0010_priv *wm0010 = spi_get_drvdata(spi);
+
+       snd_soc_unregister_codec(&spi->dev);
+
+       gpio_set_value_cansleep(wm0010->gpio_reset,
+                               wm0010->gpio_reset_value);
+
+       irq_set_irq_wake(wm0010->irq, 0);
+
+       if (wm0010->irq)
+               free_irq(wm0010->irq, wm0010);
+
+       return 0;
+}
+
+static struct spi_driver wm0010_spi_driver = {
+       .driver = {
+               .name   = "wm0010",
+               .bus    = &spi_bus_type,
+               .owner  = THIS_MODULE,
+       },
+       .probe          = wm0010_spi_probe,
+       .remove         = wm0010_spi_remove,
+};
+
+module_spi_driver(wm0010_spi_driver);
+
+MODULE_DESCRIPTION("ASoC WM0010 driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");