// SPDX-License-Identifier: GPL-2.0-only
/*
* wm8350.c -- WM8350 ALSA SoC audio driver
*
* Copyright (C) 2007-12 Wolfson Microelectronics PLC.
*
* Author: Liam Girdwood <lrg@slimlogic.co.uk>
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/mfd/wm8350/audio.h>
#include <linux/mfd/wm8350/core.h>
#include <linux/regulator/consumer.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 <trace/events/asoc.h>
#include "wm8350.h"
#define WM8350_OUTn_0dB 0x39
#define WM8350_RAMP_NONE 0
#define WM8350_RAMP_UP 1
#define WM8350_RAMP_DOWN 2
/* We only include the analogue supplies here; the digital supplies
* need to be available well before this driver can be probed.
*/
static const char *supply_names[] = {
"AVDD",
"HPVDD",
};
struct wm8350_output {
u16 active;
u16 left_vol;
u16 right_vol;
u16 ramp;
u16 mute;
};
struct wm8350_jack_data {
struct snd_soc_jack *jack;
struct delayed_work work;
int report;
int short_report;
};
struct wm8350_data {
struct wm8350 *wm8350;
struct wm8350_output out1;
struct wm8350_output out2;
struct wm8350_jack_data hpl;
struct wm8350_jack_data hpr;
struct wm8350_jack_data mic;
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
int fll_freq_out;
int fll_freq_in;
struct delayed_work pga_work;
};
/*
* Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown.
*/
static inline int wm8350_out1_ramp_step(struct wm8350_data *wm8350_data)
{
struct wm8350_output *out1 = &wm8350_data->out1;
struct wm8350 *wm8350 = wm8350_data->wm8350;
int left_complete = 0, right_complete = 0;
u16 reg, val;
/* left channel */
reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME);
val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
if (out1->ramp == WM8350_RAMP_UP) {
/* ramp step up */
if (val < out1->left_vol) {
val++;
reg &= ~WM8350_OUT1L_VOL_MASK;
wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
reg | (val << WM8350_OUT1L_VOL_SHIFT));
} else
left_complete = 1;
} else if (out1->ramp == WM8350_RAMP_DOWN) {
/* ramp step down */
if (val > 0) {
val--;
reg &= ~WM8350_OUT1L_VOL_MASK;
wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
reg | (val << WM8350_OUT1L_VOL_SHIFT));
} else
left_complete = 1;
} else
return 1;
/* right channel */
reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME);
val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
if (out1->ramp == WM8350_RAMP_UP) {
/* ramp step up */
if (val < out1->right_vol) {
val++;
reg &= ~WM8350_OUT1R_VOL_MASK;
wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
reg | (val << WM8350_OUT1R_VOL_SHIFT));
} else
right_complete = 1;
} else if (out1->ramp == WM8350_RAMP_DOWN) {
/* ramp step down */
if (val > 0) {
val--;
reg &= ~WM8350_OUT1R_VOL_MASK;
wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
reg | (val << WM8350_OUT1R_VOL_SHIFT));
} else
right_complete = 1;
}
/* only hit the update bit if either volume has changed this step */
if (!left_complete || !right_complete)
wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
return left_complete & right_complete;
}
/*
* Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown.
*/
static inline int wm8350_out2_ramp_step(struct wm8350_data *wm8350_data)
{
struct wm8350_output *out2 = &wm8350_data->out2;
struct wm8350 *wm8350 = wm8350_data->wm8350;
int left_complete = 0, right_complete = 0;
u16 reg, val;
/* left channel */
reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME);
val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
if (out2->ramp == WM8350_RAMP_UP) {
/* ramp step up */
if (val < out2->left_vol) {
val++;
reg &= ~WM8350_OUT2L_VOL_MASK;
wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
reg | (val &
|