/*
* wm_hubs.c -- WM8993/4 common code
*
* Copyright 2009 Wolfson Microelectronics plc
*
* Author: Mark Brown <broonie@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/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include "wm8993.h"
#include "wm_hubs.h"
const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0);
EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv);
static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0);
static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0);
static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1);
static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0);
static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0);
static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1);
static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0);
static const unsigned int spkboost_tlv[] = {
TLV_DB_RANGE_HEAD(7),
0, 6, TLV_DB_SCALE_ITEM(0, 150, 0),
7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0),
};
static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0);
static const char *speaker_ref_text[] = {
"SPKVDD/2",
"VMID",
};
static const struct soc_enum speaker_ref =
SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text);
static const char *speaker_mode_text[] = {
"Class D",
"Class AB",
};
static const struct soc_enum speaker_mode =
SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text);
static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
{
unsigned int reg;
int count = 0;
unsigned int val;
val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1;
/* Trigger the command */
snd_soc_write(codec, WM8993_DC_SERVO_0, val);
dev_dbg(codec->dev, "Waiting for DC servo...\n");
do {
count++;
msleep(1);
reg = snd_soc_read(codec, WM8993_DC_SERVO_0);
dev_dbg(codec->dev, "DC servo: %x\n", reg);
} while (reg & op && count < 400);
if (reg & op)
dev_err(codec->dev, "Timed out waiting for DC Servo %x\n",
op);
}
/*
* Startup calibration of the DC servo
*/
static void calibrate_dc_servo(struct snd_soc_codec *codec)
{
struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
s8 offset;
u16 reg, reg_l, reg_r, dcs_cfg;
/* If we're using a digital only path and have a previously
* callibrated DC servo offset stored then use that. */
if (hubs->class_w && hubs->class_w_dcs) {
dev_dbg(codec->dev, "Using cached DC servo offset %x\n",
hubs->class_w_dcs);
snd_soc_write(codec, WM8993_DC_SERVO_3, hubs->class_w_dcs);
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_DAC_WR_0 |
WM8993_DCS_TRIG_DAC_WR_1);
return;
}
/* Devices not using a DCS code correction have startup mode */
if (hubs->dcs_codes) {
/* Set for 32 series updates */
snd_soc_update_bits(codec, WM8993_DC_SERVO_1,
WM8993_DCS_SERIES_NO_01_MASK,
32 << WM8993_DCS_SERIES_NO_01_SHIFT);
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_SERIES_0 |
WM8993_DCS_TRIG_SERIES_1);
} else {
wait_for_dc_servo(codec,
WM8993_DCS_TRIG_STARTUP_0 |
WM8993_DCS_TRIG_STARTUP_1);
}
/* Different chips in the family support different readback
* methods.
*/
switch (hubs->dcs_readback_mode) {
case 0:
reg_l = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_1)
& WM8993_DCS_INTEG_CHAN_0_MASK;
reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)
& WM8993_DCS_INTEG_CHAN_1_MASK;
break;
case 1:
reg = snd_soc_read(codec, WM8993_DC_SERVO_3);
reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)
>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;
reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK;
break;
default:
WARN(1, "Unknown DCS readback method\n");
break;
}
dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r);
/* Apply correction to DC servo result */
if (hubs->dcs_codes) {
dev_dbg(codec->dev, "Applying %d code DC servo correction\n",
hubs->dcs_codes);
/* HPOUT1L */
offset = reg_l;
offset += hubs-&