diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /sound/soc/codecs/wm0010.c | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'sound/soc/codecs/wm0010.c')
| -rw-r--r-- | sound/soc/codecs/wm0010.c | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c new file mode 100644 index 000000000..c44abffe9 --- /dev/null +++ b/sound/soc/codecs/wm0010.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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> + */ + +#include <linux/cleanup.h> +#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/gpio/consumer.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[]; +} __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_component *component; + + struct mutex lock; + struct device *dev; + + struct wm0010_pdata pdata; + + struct gpio_desc *reset; + + 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; +}; + +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_component *component) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + enum wm0010_state state; + + /* Fetch the wm0010 state */ + scoped_guard(spinlock_irqsave, &wm0010->irq_lock) + state = wm0010->state; + + 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 */ + gpiod_set_value_cansleep(wm0010->reset, 1); + /* Disable the regulators */ + regulator_disable(wm0010->dbvdd); + regulator_bulk_disable(ARRAY_SIZE(wm0010->core_supplies), + wm0010->core_supplies); + break; + } + + scoped_guard(spinlock_irqsave, &wm0010->irq_lock) + wm0010->state = WM0010_POWER_OFF; +} + +struct wm0010_boot_xfer { + struct list_head list; + struct snd_soc_component *component; + 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; + + scoped_guard(spinlock_irqsave, &wm0010->irq_lock) + state = wm0010->state; + + 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_component *component = xfer->component; + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + u32 *out32 = xfer->t.rx_buf; + int i; + + if (xfer->m.status != 0) { + dev_err(component->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(component->dev, "%d: %04x\n", i, out32[i]); + + switch (be32_to_cpu(out32[i])) { + case 0xe0e0e0e0: + dev_err(component->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(component->dev, + "%d: ROM bootloader running in stage 2\n", i); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0000: + dev_dbg(component->dev, "Stage2 loader running\n"); + break; + + case 0x0fed0007: + dev_dbg(component->dev, "CODE_HDR packet received\n"); + break; + + case 0x0fed0008: + dev_dbg(component->dev, "CODE_DATA packet received\n"); + break; + + case 0x0fed0009: + dev_dbg(component->dev, "Download complete\n"); + break; + + case 0x0fed000c: + dev_dbg(component->dev, "Application start\n"); + break; + + case 0x0fed000e: + dev_dbg(component->dev, "PLL packet received\n"); + wm0010->pll_running = true; + break; + + case 0x0fed0025: + dev_err(component->dev, "Device reports image too long\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed002c: + dev_err(component->dev, "Device reports bad SPI packet\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0031: + dev_err(component->dev, "Device reports SPI read overflow\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0032: + dev_err(component->dev, "Device reports SPI underclock\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0033: + dev_err(component->dev, "Device reports bad header packet\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0034: + dev_err(component->dev, "Device reports invalid packet type\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0035: + dev_err(component->dev, "Device reports data before header error\n"); + wm0010_mark_boot_failure(wm0010); + break; + + case 0x0fed0038: + dev_err(component->dev, "Device reports invalid PLL packet\n"); + break; + + case 0x0fed003a: + dev_err(component->dev, "Device reports packet alignment error\n"); + wm0010_mark_boot_failure(wm0010); + break; + + default: + dev_err(component->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] = swab64(data_in[i]); +} + +static int wm0010_firmware_load(const char *name, struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + struct list_head xfer_list; + struct wm0010_boot_xfer *xfer; + int ret; + DECLARE_COMPLETION_ONSTACK(done); + const struct dfw_binrec *rec; + const struct dfw_inforec *inforec; + u64 *img; + u8 *out, dsp; + u32 len, offset; + + INIT_LIST_HEAD(&xfer_list); + + const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, name, component->dev); + if (ret != 0) { + dev_err(component->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; + + /* First record should be INFO */ + if (rec->command != DFW_CMD_INFO) { + dev_err(component->dev, "First record not INFO\r\n"); + return -EINVAL; + } + + if (inforec->info_version != INFO_VERSION) { + dev_err(component->dev, + "Unsupported version (%02d) of INFO record\r\n", + inforec->info_version); + return -EINVAL; + } + + dev_dbg(component->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(component->dev, "Not a WM0010 firmware file.\r\n"); + return -EINVAL; + } + + /* 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(component->dev, + "Packet: command %d, data length = 0x%x\r\n", + rec->command, rec->length); + len = rec->length + 8; + + xfer = kzalloc_obj(*xfer); + if (!xfer) { + ret = -ENOMEM; + goto abort; + } + + xfer->component = component; + list_add_tail(&xfer->list, &xfer_list); + + out = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!out) { + ret = -ENOMEM; + goto abort; + } + xfer->t.rx_buf = out; + + img = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img) { + ret = -ENOMEM; + goto abort; + } + 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(component->dev, "All transfers scheduled\n"); + xfer->done = &done; + } + + ret = spi_async(spi, &xfer->m); + if (ret != 0) { + dev_err(component->dev, "Write failed: %d\n", ret); + goto abort; + } + + if (wm0010->boot_failed) { + dev_dbg(component->dev, "Boot fail!\n"); + ret = -EINVAL; + goto abort; + } + } + + wait_for_completion(&done); + + ret = 0; + +abort: + 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); + } + + return ret; +} + +static int wm0010_stage2_load(struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + struct spi_message m; + struct spi_transfer t; + int i; + int ret = 0; + + const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, "wm0010_stage2.bin", component->dev); + if (ret != 0) { + dev_err(component->dev, "Failed to request stage2 loader: %d\n", + ret); + return ret; + } + + dev_dbg(component->dev, "Downloading %zu byte stage 2 loader\n", fw->size); + + /* Copy to local buffer first as vmalloc causes problems for dma */ + u32 *img __free(kfree) = + kmemdup(&fw->data[0], fw->size, GFP_KERNEL | GFP_DMA); + if (!img) + return -ENOMEM; + + u8 *out __free(kfree) = + kzalloc(fw->size, GFP_KERNEL | GFP_DMA); + if (!out) + return -ENOMEM; + + 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(component->dev, "Starting initial download at %dHz\n", + t.speed_hz); + + ret = spi_sync(spi, &m); + if (ret != 0) { + dev_err(component->dev, "Initial download failed: %d\n", ret); + return ret; + } + + /* Look for errors from the boot ROM */ + for (i = 0; i < fw->size; i++) { + if (out[i] != 0x55) { + dev_err(component->dev, "Boot ROM error: %x in %d\n", + out[i], i); + wm0010_mark_boot_failure(wm0010); + return -EBUSY; + } + } + + return 0; +} + +static int wm0010_boot(struct snd_soc_component *component) +{ + struct spi_device *spi = to_spi_device(component->dev); + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + unsigned long flags; + int ret; + 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(component->dev, "Max DSP clock frequency is 26MHz\n"); + ret = -ECANCELED; + goto err; + } + + mutex_lock(&wm0010->lock); + wm0010->pll_running = false; + + dev_dbg(component->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 */ + gpiod_set_value_cansleep(wm0010->reset, 0); + spin_lock_irqsave(&wm0010->irq_lock, flags); + wm0010->state = WM0010_OUT_OF_RESET; + spin_unlock_irqrestore(&wm0010->irq_lock, flags); + + if (!wait_for_completion_timeout(&wm0010->boot_completion, + msecs_to_jiffies(20))) + dev_err(component->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(component); + if (ret) + goto abort; + + if (!wait_for_completion_timeout(&wm0010->boot_completion, + msecs_to_jiffies(20))) + dev_err(component->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) + goto abort; + + img_swap = kzalloc(len, GFP_KERNEL | GFP_DMA); + if (!img_swap) + goto abort_out; + + /* 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) { + dev_err(component->dev, "First PLL write failed: %d\n", ret); + goto abort_swap; + } + + /* Use a second send of the message to get the return status */ + ret = spi_sync(spi, &m); + if (ret) { + dev_err(component->dev, "Second PLL write failed: %d\n", ret); + goto abort_swap; + } + + p = (u32 *)out; + + /* Look for PLL active code from the DSP */ + for (i = 0; i < len / 4; i++) { + if (*p == 0x0e00ed0f) { + dev_dbg(component->dev, "PLL packet received\n"); + wm0010->pll_running = true; + break; + } + p++; + } + + kfree(img_swap); + kfree(out); + } else + dev_dbg(component->dev, "Not enabling DSP PLL."); + + ret = wm0010_firmware_load("wm0010.dfw", component); + + 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_swap: + kfree(img_swap); +abort_out: + kfree(out); +abort: + /* Put the chip back into reset */ + wm0010_halt(component); + 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_component *component, + enum snd_soc_bias_level level) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + + switch (level) { + case SND_SOC_BIAS_ON: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) + wm0010_boot(component); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_PREPARE) { + scoped_guard(mutex, &wm0010->lock) + wm0010_halt(component); + } + break; + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +static int wm0010_set_sysclk(struct snd_soc_component *component, int source, + int clk_id, unsigned int freq, int dir) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + 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_component *component); + +static const struct snd_soc_component_driver soc_component_dev_wm0010 = { + .probe = wm0010_probe, + .set_bias_level = wm0010_set_bias_level, + .set_sysclk = wm0010_set_sysclk, + .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), + .use_pmdown_time = 1, + .endianness = 1, +}; + +#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: + scoped_guard(spinlock, &wm0010->irq_lock) + complete(&wm0010->boot_completion); + return IRQ_HANDLED; + default: + return IRQ_NONE; + } + + return IRQ_NONE; +} + +static int wm0010_probe(struct snd_soc_component *component) +{ + struct wm0010_priv *wm0010 = snd_soc_component_get_drvdata(component); + + wm0010->component = component; + + return 0; +} + +static int wm0010_spi_probe(struct spi_device *spi) +{ + 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; + } + + wm0010->reset = devm_gpiod_get(wm0010->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(wm0010->reset)) + return dev_err_probe(wm0010->dev, PTR_ERR(wm0010->reset), + "could not get RESET GPIO\n"); + gpiod_set_consumer_name(wm0010->reset, "wm0010 reset"); + + 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, + "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); + goto free_irq; + } + + if (spi->max_speed_hz) + wm0010->board_max_spi_speed = spi->max_speed_hz; + else + wm0010->board_max_spi_speed = 0; + + ret = devm_snd_soc_register_component(&spi->dev, + &soc_component_dev_wm0010, wm0010_dai, + ARRAY_SIZE(wm0010_dai)); + if (ret < 0) + goto disable_irq_wake; + + return 0; + +disable_irq_wake: + irq_set_irq_wake(wm0010->irq, 0); + +free_irq: + if (wm0010->irq) + free_irq(wm0010->irq, wm0010); + + return ret; +} + +static void wm0010_spi_remove(struct spi_device *spi) +{ + struct wm0010_priv *wm0010 = spi_get_drvdata(spi); + + gpiod_set_value_cansleep(wm0010->reset, 1); + + irq_set_irq_wake(wm0010->irq, 0); + + if (wm0010->irq) + free_irq(wm0010->irq, wm0010); +} + +static struct spi_driver wm0010_spi_driver = { + .driver = { + .name = "wm0010", + }, + .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"); + +MODULE_FIRMWARE("wm0010.dfw"); +MODULE_FIRMWARE("wm0010_stage2.bin"); |
