/*
* 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)
{
const char *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