2024-04-07 09:18:42 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//
|
|
|
|
// ALSA SoC Texas Instruments PCM6240 Family Audio ADC/DAC Device
|
|
|
|
//
|
|
|
|
// Copyright (C) 2022 - 2024 Texas Instruments Incorporated
|
|
|
|
// https://www.ti.com
|
|
|
|
//
|
|
|
|
// The PCM6240 driver implements a flexible and configurable
|
|
|
|
// algo coefficient setting for one, two, or even multiple
|
|
|
|
// PCM6240 Family chips.
|
|
|
|
//
|
|
|
|
// Author: Shenghao Ding <shenghao-ding@ti.com>
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <asm/unaligned.h>
|
|
|
|
#include <linux/firmware.h>
|
|
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of_irq.h>
|
2024-07-02 21:53:49 +00:00
|
|
|
#include <linux/of_address.h>
|
2024-04-07 09:18:42 +00:00
|
|
|
#include <linux/regmap.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <sound/tlv.h>
|
|
|
|
|
|
|
|
#include "pcm6240.h"
|
|
|
|
|
|
|
|
static const struct i2c_device_id pcmdevice_i2c_id[] = {
|
|
|
|
{ "adc3120", ADC3120 },
|
|
|
|
{ "adc5120", ADC5120 },
|
|
|
|
{ "adc6120", ADC6120 },
|
|
|
|
{ "dix4192", DIX4192 },
|
|
|
|
{ "pcm1690", PCM1690 },
|
|
|
|
{ "pcm3120", PCM3120 },
|
|
|
|
{ "pcm3140", PCM3140 },
|
|
|
|
{ "pcm5120", PCM5120 },
|
|
|
|
{ "pcm5140", PCM5140 },
|
|
|
|
{ "pcm6120", PCM6120 },
|
|
|
|
{ "pcm6140", PCM6140 },
|
|
|
|
{ "pcm6240", PCM6240 },
|
|
|
|
{ "pcm6260", PCM6260 },
|
|
|
|
{ "pcm9211", PCM9211 },
|
|
|
|
{ "pcmd3140", PCMD3140 },
|
|
|
|
{ "pcmd3180", PCMD3180 },
|
|
|
|
{ "pcmd512x", PCMD512X },
|
|
|
|
{ "taa5212", TAA5212 },
|
|
|
|
{ "taa5412", TAA5412 },
|
|
|
|
{ "tad5212", TAD5212 },
|
|
|
|
{ "tad5412", TAD5412 },
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, pcmdevice_i2c_id);
|
|
|
|
|
|
|
|
static const char *const pcmdev_ctrl_name[] = {
|
|
|
|
"%s i2c%d Dev%d Ch%d Ana Volume",
|
|
|
|
"%s i2c%d Dev%d Ch%d Digi Volume",
|
|
|
|
"%s i2c%d Dev%d Ch%d Fine Volume",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control adc5120_analog_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 1,
|
|
|
|
.reg = ADC5120_REG_CH1_ANALOG_GAIN,
|
|
|
|
.max = 0x54,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 1,
|
|
|
|
.reg = ADC5120_REG_CH2_ANALOG_GAIN,
|
|
|
|
.max = 0x54,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control adc5120_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = ADC5120_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = ADC5120_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm1690_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH3_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH4_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH5_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH6_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH7_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM1690_REG_CH8_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm6240_analog_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6240_REG_CH1_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6240_REG_CH2_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6240_REG_CH3_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6240_REG_CH4_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm6240_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6240_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6240_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6240_REG_CH3_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6240_REG_CH4_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm6260_analog_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH1_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH2_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH3_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH4_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH5_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 2,
|
|
|
|
.reg = PCM6260_REG_CH6_ANALOG_GAIN,
|
|
|
|
.max = 0x42,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm6260_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH3_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH4_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH5_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM6260_REG_CH6_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcm9211_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM9211_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCM9211_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcmd3140_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3140_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3140_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3140_REG_CH3_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3140_REG_CH4_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcmd3140_fine_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3140_REG_CH1_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3140_REG_CH2_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3140_REG_CH3_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3140_REG_CH4_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcmd3180_digi_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH1_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH2_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH3_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH4_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH5_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH6_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH7_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = PCMD3180_REG_CH8_DIGITAL_GAIN,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control pcmd3180_fine_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH1_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH2_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH3_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH4_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH5_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH6_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH7_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = PCMD3180_REG_CH8_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control taa5412_digi_vol_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = TAA5412_REG_CH1_DIGITAL_VOLUME,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = TAA5412_REG_CH2_DIGITAL_VOLUME,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = TAA5412_REG_CH3_DIGITAL_VOLUME,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = TAA5412_REG_CH4_DIGITAL_VOLUME,
|
|
|
|
.max = 0xff,
|
|
|
|
.invert = 0,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pcmdevice_mixer_control taa5412_fine_gain_ctl[] = {
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = TAA5412_REG_CH1_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = TAA5412_REG_CH2_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 4,
|
|
|
|
.reg = TAA5412_REG_CH3_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 4,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.shift = 0,
|
|
|
|
.reg = TAA5412_REG_CH4_FINE_GAIN,
|
|
|
|
.max = 0xf,
|
|
|
|
.invert = 4,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(pcmd3140_dig_gain_tlv,
|
|
|
|
-10000, 2700);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_fine_dig_gain_tlv,
|
|
|
|
-12750, 0);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(pcm1690_dig_gain_tlv,
|
|
|
|
-25500, 0);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(pcm9211_dig_gain_tlv,
|
|
|
|
-11450, 2000);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(adc5120_fgain_tlv,
|
|
|
|
-10050, 2700);
|
|
|
|
static const DECLARE_TLV_DB_LINEAR(adc5120_chgain_tlv, 0, 4200);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(pcm6260_fgain_tlv,
|
|
|
|
-10000, 2700);
|
|
|
|
static const DECLARE_TLV_DB_LINEAR(pcm6260_chgain_tlv, 0, 4200);
|
|
|
|
static const DECLARE_TLV_DB_MINMAX_MUTE(taa5412_dig_vol_tlv,
|
|
|
|
-8050, 4700);
|
|
|
|
static const DECLARE_TLV_DB_LINEAR(taa5412_fine_gain_tlv,
|
|
|
|
-80, 70);
|
|
|
|
|
|
|
|
static int pcmdev_change_dev(struct pcmdevice_priv *pcm_priv,
|
|
|
|
unsigned short dev_no)
|
|
|
|
{
|
|
|
|
struct i2c_client *client = (struct i2c_client *)pcm_priv->client;
|
|
|
|
struct regmap *map = pcm_priv->regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (client->addr == pcm_priv->addr[dev_no])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
client->addr = pcm_priv->addr[dev_no];
|
|
|
|
/* All pcmdevices share the same regmap, clear the page
|
|
|
|
* inside regmap once switching to another pcmdevice.
|
|
|
|
* Register 0 at any pages inside pcmdevice is the same
|
|
|
|
* one for page-switching.
|
|
|
|
*/
|
|
|
|
ret = regmap_write(map, PCMDEVICE_PAGE_SELECT, 0);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_priv->dev, "%s: err = %d\n", __func__, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_dev_read(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned int dev_no, unsigned int reg, unsigned int *val)
|
|
|
|
{
|
|
|
|
struct regmap *map = pcm_dev->regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dev_no >= pcm_dev->ndev) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__,
|
|
|
|
dev_no);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_change_dev(pcm_dev, dev_no);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = regmap_read(map, reg, val);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: err = %d\n", __func__, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_dev_update_bits(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned int dev_no, unsigned int reg, unsigned int mask,
|
|
|
|
unsigned int value)
|
|
|
|
{
|
|
|
|
struct regmap *map = pcm_dev->regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dev_no >= pcm_dev->ndev) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__,
|
|
|
|
dev_no);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_change_dev(pcm_dev, dev_no);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = regmap_update_bits(map, reg, mask, value);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: update_bits err=%d\n",
|
|
|
|
__func__, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol, int vol_ctrl_type)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct pcmdevice_priv *pcm_dev =
|
|
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
struct pcmdevice_mixer_control *mc =
|
|
|
|
(struct pcmdevice_mixer_control *)kcontrol->private_value;
|
|
|
|
int max = mc->max, ret;
|
|
|
|
unsigned int mask = BIT(fls(max)) - 1;
|
|
|
|
unsigned int dev_no = mc->dev_no;
|
|
|
|
unsigned int shift = mc->shift;
|
|
|
|
unsigned int reg = mc->reg;
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
mutex_lock(&pcm_dev->codec_lock);
|
|
|
|
|
|
|
|
if (pcm_dev->chip_id == PCM1690) {
|
|
|
|
ret = pcmdev_dev_read(pcm_dev, dev_no, PCM1690_REG_MODE_CTRL,
|
|
|
|
&val);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: read mode err=%d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
val &= PCM1690_REG_MODE_CTRL_DAMS_MSK;
|
|
|
|
/* Set to wide-range mode, before using vol ctrl. */
|
|
|
|
if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) {
|
|
|
|
ucontrol->value.integer.value[0] = -25500;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
/* Set to fine mode, before using fine vol ctrl. */
|
|
|
|
if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) {
|
|
|
|
ucontrol->value.integer.value[0] = -12750;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_dev_read(pcm_dev, dev_no, reg, &val);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: read err=%d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
val = (val >> shift) & mask;
|
|
|
|
val = (val > max) ? max : val;
|
|
|
|
val = mc->invert ? max - val : val;
|
|
|
|
ucontrol->value.integer.value[0] = val;
|
|
|
|
out:
|
|
|
|
mutex_unlock(&pcm_dev->codec_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_get_volsw(kcontrol, ucontrol, PCMDEV_GENERIC_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm1690_get_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_get_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm1690_get_finevolsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_get_volsw(kcontrol, ucontrol,
|
|
|
|
PCMDEV_PCM1690_FINE_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_put_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol, int vol_ctrl_type)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct pcmdevice_priv *pcm_dev =
|
|
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
struct pcmdevice_mixer_control *mc =
|
|
|
|
(struct pcmdevice_mixer_control *)kcontrol->private_value;
|
|
|
|
int max = mc->max, rc;
|
|
|
|
unsigned int mask = BIT(fls(max)) - 1;
|
|
|
|
unsigned int dev_no = mc->dev_no;
|
|
|
|
unsigned int shift = mc->shift;
|
|
|
|
unsigned int val, val_mask;
|
|
|
|
unsigned int reg = mc->reg;
|
|
|
|
|
|
|
|
mutex_lock(&pcm_dev->codec_lock);
|
|
|
|
val = ucontrol->value.integer.value[0] & mask;
|
|
|
|
val = (val > max) ? max : val;
|
|
|
|
val = mc->invert ? max - val : val;
|
|
|
|
val_mask = mask << shift;
|
|
|
|
val = val << shift;
|
|
|
|
|
|
|
|
switch (vol_ctrl_type) {
|
|
|
|
case PCMDEV_PCM1690_VOL_CTRL:
|
|
|
|
val_mask |= PCM1690_REG_MODE_CTRL_DAMS_MSK;
|
|
|
|
val |= PCM1690_REG_MODE_CTRL_DAMS_WIDE_RANGE;
|
|
|
|
break;
|
|
|
|
case PCMDEV_PCM1690_FINE_VOL_CTRL:
|
|
|
|
val_mask |= PCM1690_REG_MODE_CTRL_DAMS_MSK;
|
|
|
|
val |= PCM1690_REG_MODE_CTRL_DAMS_FINE_STEP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = pcmdev_dev_update_bits(pcm_dev, dev_no, reg, val_mask, val);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: update_bits err = %d\n",
|
|
|
|
__func__, rc);
|
|
|
|
else
|
|
|
|
rc = 1;
|
|
|
|
mutex_unlock(&pcm_dev->codec_lock);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_put_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_put_volsw(kcontrol, ucontrol, PCMDEV_GENERIC_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm1690_put_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_put_volsw(kcontrol, ucontrol, PCMDEV_PCM1690_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcm1690_put_finevolsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
return pcmdev_put_volsw(kcontrol, ucontrol,
|
|
|
|
PCMDEV_PCM1690_FINE_VOL_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct pcmdev_ctrl_info pcmdev_gain_ctl_info[][2] = {
|
|
|
|
// ADC3120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// ADC5120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// ADC6120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// DIX4192
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM1690
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm1690_fine_dig_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm1690_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm1690_digi_gain_ctl),
|
|
|
|
.get = pcm1690_get_volsw,
|
|
|
|
.put = pcm1690_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm1690_dig_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm1690_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm1690_digi_gain_ctl),
|
|
|
|
.get = pcm1690_get_finevolsw,
|
|
|
|
.put = pcm1690_put_finevolsw,
|
|
|
|
.pcmdev_ctrl_name_id = 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM3120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM3140
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm6260_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm6260_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM5120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM5140
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm6260_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm6260_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM6120
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = adc5120_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = adc5120_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = adc5120_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(adc5120_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM6140
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm6260_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm6260_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM6240
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm6260_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm6260_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6240_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6240_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM6260
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = pcm6260_chgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6260_analog_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6260_analog_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm6260_fgain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm6260_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm6260_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCM9211
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcm9211_dig_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcm9211_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcm9211_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
// PCMD3140
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = taa5412_fine_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcmd3140_fine_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcmd3140_fine_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 2,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcmd3140_dig_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcmd3140_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcmd3140_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCMD3180
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = taa5412_fine_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcmd3180_fine_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcmd3180_fine_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 2,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = pcmd3140_dig_gain_tlv,
|
|
|
|
.pcmdev_ctrl = pcmd3180_digi_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(pcmd3180_digi_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// PCMD512X
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// TAA5212
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = taa5412_fine_gain_tlv,
|
|
|
|
.pcmdev_ctrl = taa5412_fine_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(taa5412_fine_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 2,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = taa5412_dig_vol_tlv,
|
|
|
|
.pcmdev_ctrl = taa5412_digi_vol_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(taa5412_digi_vol_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// TAA5412
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.gain = taa5412_fine_gain_tlv,
|
|
|
|
.pcmdev_ctrl = taa5412_fine_gain_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(taa5412_fine_gain_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 2,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.gain = taa5412_dig_vol_tlv,
|
|
|
|
.pcmdev_ctrl = taa5412_digi_vol_ctl,
|
|
|
|
.ctrl_array_size = ARRAY_SIZE(taa5412_digi_vol_ctl),
|
|
|
|
.get = pcmdevice_get_volsw,
|
|
|
|
.put = pcmdevice_put_volsw,
|
|
|
|
.pcmdev_ctrl_name_id = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// TAD5212
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// TAD5412
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.ctrl_array_size = 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pcmdev_dev_bulk_write(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned int dev_no, unsigned int reg, unsigned char *data,
|
|
|
|
unsigned int len)
|
|
|
|
{
|
|
|
|
struct regmap *map = pcm_dev->regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dev_no >= pcm_dev->ndev) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__,
|
|
|
|
dev_no);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_change_dev(pcm_dev, dev_no);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = regmap_bulk_write(map, reg, data, len);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: bulk_write err = %d\n", __func__,
|
|
|
|
ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_dev_write(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned int dev_no, unsigned int reg, unsigned int value)
|
|
|
|
{
|
|
|
|
struct regmap *map = pcm_dev->regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dev_no >= pcm_dev->ndev) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: no such channel(%d)\n", __func__,
|
|
|
|
dev_no);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_change_dev(pcm_dev, dev_no);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: chg dev err = %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = regmap_write(map, reg, value);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: err = %d\n", __func__, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_info_profile(
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *codec
|
|
|
|
= snd_soc_kcontrol_component(kcontrol);
|
|
|
|
struct pcmdevice_priv *pcm_dev =
|
|
|
|
snd_soc_component_get_drvdata(codec);
|
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = max(0, pcm_dev->regbin.ncfgs - 1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_get_profile_id(
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *codec
|
|
|
|
= snd_soc_kcontrol_component(kcontrol);
|
|
|
|
struct pcmdevice_priv *pcm_dev =
|
|
|
|
snd_soc_component_get_drvdata(codec);
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] = pcm_dev->cur_conf;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_set_profile_id(
|
|
|
|
struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *codec
|
|
|
|
= snd_soc_kcontrol_component(kcontrol);
|
|
|
|
struct pcmdevice_priv *pcm_dev =
|
|
|
|
snd_soc_component_get_drvdata(codec);
|
|
|
|
int nr_profile = ucontrol->value.integer.value[0];
|
|
|
|
int max = pcm_dev->regbin.ncfgs - 1;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
nr_profile = clamp(nr_profile, 0, max);
|
|
|
|
|
|
|
|
if (pcm_dev->cur_conf != nr_profile) {
|
|
|
|
pcm_dev->cur_conf = nr_profile;
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_info_volsw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
struct pcmdevice_mixer_control *mc =
|
|
|
|
(struct pcmdevice_mixer_control *)kcontrol->private_value;
|
|
|
|
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 0;
|
|
|
|
uinfo->value.integer.max = mc->max;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcm9211_sw_rst(struct pcmdevice_priv *pcm_dev)
|
|
|
|
{
|
|
|
|
int ret, i;
|
|
|
|
|
|
|
|
for (i = 0; i < pcm_dev->ndev; i++) {
|
|
|
|
ret = pcmdev_dev_update_bits(pcm_dev, i,
|
|
|
|
PCM9211_REG_SW_CTRL, PCM9211_REG_SW_CTRL_MRST_MSK,
|
|
|
|
PCM9211_REG_SW_CTRL_MRST);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev %d swreset fail %d\n",
|
|
|
|
__func__, i, ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcmdevice_sw_rst(struct pcmdevice_priv *pcm_dev)
|
|
|
|
{
|
|
|
|
int ret, i;
|
|
|
|
|
|
|
|
for (i = 0; i < pcm_dev->ndev; i++) {
|
|
|
|
ret = pcmdev_dev_write(pcm_dev, i, PCMDEVICE_REG_SWRESET,
|
|
|
|
PCMDEVICE_REG_SWRESET_RESET);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev %d swreset fail %d\n",
|
|
|
|
__func__, i, ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pcmdevice_config_info *pcmdevice_add_config(void *ctxt,
|
|
|
|
const unsigned char *config_data, unsigned int config_size,
|
|
|
|
int *status)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
|
|
|
|
struct pcmdevice_config_info *cfg_info;
|
|
|
|
struct pcmdevice_block_data **bk_da;
|
|
|
|
unsigned int config_offset = 0, i;
|
|
|
|
|
|
|
|
cfg_info = kzalloc(sizeof(struct pcmdevice_config_info), GFP_KERNEL);
|
|
|
|
if (!cfg_info) {
|
|
|
|
*status = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pcm_dev->regbin.fw_hdr.binary_version_num >= 0x105) {
|
|
|
|
if (config_offset + 64 > (int)config_size) {
|
|
|
|
*status = -EINVAL;
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: cfg_name out of boundary\n", __func__);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
memcpy(cfg_info->cfg_name, &config_data[config_offset], 64);
|
|
|
|
config_offset += 64;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config_offset + 4 > config_size) {
|
|
|
|
*status = -EINVAL;
|
|
|
|
dev_err(pcm_dev->dev, "%s: nblocks out of boundary\n",
|
|
|
|
__func__);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
cfg_info->nblocks =
|
|
|
|
get_unaligned_be32(&config_data[config_offset]);
|
|
|
|
config_offset += 4;
|
|
|
|
|
|
|
|
bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks,
|
|
|
|
sizeof(struct pcmdevice_block_data *), GFP_KERNEL);
|
|
|
|
if (!bk_da) {
|
|
|
|
*status = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
cfg_info->real_nblocks = 0;
|
|
|
|
for (i = 0; i < cfg_info->nblocks; i++) {
|
|
|
|
if (config_offset + 12 > config_size) {
|
|
|
|
*status = -EINVAL;
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: out of boundary i = %d nblocks = %u\n",
|
|
|
|
__func__, i, cfg_info->nblocks);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
bk_da[i] = kzalloc(sizeof(struct pcmdevice_block_data),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!bk_da[i]) {
|
|
|
|
*status = -ENOMEM;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
bk_da[i]->dev_idx = config_data[config_offset];
|
|
|
|
config_offset++;
|
|
|
|
|
|
|
|
bk_da[i]->block_type = config_data[config_offset];
|
|
|
|
config_offset++;
|
|
|
|
|
|
|
|
if (bk_da[i]->block_type == PCMDEVICE_BIN_BLK_PRE_POWER_UP) {
|
|
|
|
if (bk_da[i]->dev_idx == 0)
|
|
|
|
cfg_info->active_dev =
|
|
|
|
(1 << pcm_dev->ndev) - 1;
|
|
|
|
else
|
|
|
|
cfg_info->active_dev =
|
|
|
|
1 << (bk_da[i]->dev_idx - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
bk_da[i]->yram_checksum =
|
|
|
|
get_unaligned_be16(&config_data[config_offset]);
|
|
|
|
config_offset += 2;
|
|
|
|
bk_da[i]->block_size =
|
|
|
|
get_unaligned_be32(&config_data[config_offset]);
|
|
|
|
config_offset += 4;
|
|
|
|
|
|
|
|
bk_da[i]->n_subblks =
|
|
|
|
get_unaligned_be32(&config_data[config_offset]);
|
|
|
|
|
|
|
|
config_offset += 4;
|
|
|
|
|
|
|
|
if (config_offset + bk_da[i]->block_size > config_size) {
|
|
|
|
*status = -EINVAL;
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: out of boundary: i = %d blks = %u\n",
|
|
|
|
__func__, i, cfg_info->nblocks);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bk_da[i]->regdata = kmemdup(&config_data[config_offset],
|
|
|
|
bk_da[i]->block_size, GFP_KERNEL);
|
|
|
|
if (!bk_da[i]->regdata) {
|
|
|
|
*status = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
config_offset += bk_da[i]->block_size;
|
|
|
|
cfg_info->real_nblocks += 1;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return cfg_info;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_gain_ctrl_add(struct pcmdevice_priv *pcm_dev,
|
|
|
|
int dev_no, int ctl_id)
|
|
|
|
{
|
|
|
|
struct i2c_adapter *adap = pcm_dev->client->adapter;
|
|
|
|
struct snd_soc_component *comp = pcm_dev->component;
|
|
|
|
struct pcmdevice_mixer_control *pcmdev_ctrl;
|
|
|
|
struct snd_kcontrol_new *pcmdev_controls;
|
|
|
|
int ret, mix_index = 0, name_id, chn;
|
|
|
|
unsigned int id = pcm_dev->chip_id;
|
|
|
|
const int nr_chn =
|
|
|
|
pcmdev_gain_ctl_info[id][ctl_id].ctrl_array_size;
|
|
|
|
const char *ctrl_name;
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
if (!nr_chn) {
|
|
|
|
dev_dbg(pcm_dev->dev, "%s: no gain ctrl for %s\n", __func__,
|
|
|
|
pcm_dev->dev_name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pcmdev_controls = devm_kzalloc(pcm_dev->dev,
|
|
|
|
nr_chn * sizeof(struct snd_kcontrol_new), GFP_KERNEL);
|
|
|
|
if (!pcmdev_controls)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
name_id = pcmdev_gain_ctl_info[id][ctl_id].pcmdev_ctrl_name_id;
|
|
|
|
|
2024-07-05 06:48:46 +00:00
|
|
|
ctrl_name = pcmdev_ctrl_name[name_id];
|
2024-04-07 09:18:42 +00:00
|
|
|
|
|
|
|
for (chn = 1; chn <= nr_chn; chn++) {
|
|
|
|
name = devm_kzalloc(pcm_dev->dev,
|
|
|
|
SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL);
|
|
|
|
if (!name) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
2024-07-05 06:48:46 +00:00
|
|
|
scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
|
|
|
|
ctrl_name, pcm_dev->upper_dev_name, adap->nr,
|
|
|
|
dev_no, chn);
|
2024-04-07 09:18:42 +00:00
|
|
|
pcmdev_controls[mix_index].tlv.p =
|
|
|
|
pcmdev_gain_ctl_info[id][ctl_id].gain;
|
|
|
|
pcmdev_ctrl = devm_kmemdup(pcm_dev->dev,
|
|
|
|
&pcmdev_gain_ctl_info[id][ctl_id].pcmdev_ctrl[chn - 1],
|
|
|
|
sizeof(*pcmdev_ctrl), GFP_KERNEL);
|
|
|
|
if (!pcmdev_ctrl) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
pcmdev_ctrl->dev_no = dev_no;
|
|
|
|
pcmdev_controls[mix_index].private_value =
|
|
|
|
(unsigned long)pcmdev_ctrl;
|
|
|
|
pcmdev_controls[mix_index].name = name;
|
|
|
|
pcmdev_controls[mix_index].access =
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
|
|
|
|
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
pcmdev_controls[mix_index].iface =
|
|
|
|
SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
|
|
pcmdev_controls[mix_index].info = pcmdevice_info_volsw;
|
|
|
|
pcmdev_controls[mix_index].get =
|
|
|
|
pcmdev_gain_ctl_info[id][ctl_id].get;
|
|
|
|
pcmdev_controls[mix_index].put =
|
|
|
|
pcmdev_gain_ctl_info[id][ctl_id].put;
|
|
|
|
mix_index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snd_soc_add_component_controls(comp, pcmdev_controls, mix_index);
|
|
|
|
if (ret)
|
|
|
|
dev_err(pcm_dev->dev, "%s: add_controls err = %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_profile_ctrl_add(struct pcmdevice_priv *pcm_dev)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *comp = pcm_dev->component;
|
|
|
|
struct i2c_adapter *adap = pcm_dev->client->adapter;
|
|
|
|
struct snd_kcontrol_new *pcmdev_ctrl;
|
|
|
|
char *name;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pcmdev_ctrl = devm_kzalloc(pcm_dev->dev,
|
|
|
|
sizeof(struct snd_kcontrol_new), GFP_KERNEL);
|
|
|
|
if (!pcmdev_ctrl)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Create a mixer item for selecting the active profile */
|
|
|
|
name = devm_kzalloc(pcm_dev->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!name)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2024-07-05 06:48:46 +00:00
|
|
|
scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
|
|
|
|
"%s i2c%d Profile id", pcm_dev->upper_dev_name, adap->nr);
|
2024-04-07 09:18:42 +00:00
|
|
|
pcmdev_ctrl->name = name;
|
|
|
|
pcmdev_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
|
|
pcmdev_ctrl->info = pcmdevice_info_profile;
|
|
|
|
pcmdev_ctrl->get = pcmdevice_get_profile_id;
|
|
|
|
pcmdev_ctrl->put = pcmdevice_set_profile_id;
|
|
|
|
|
|
|
|
ret = snd_soc_add_component_controls(comp, pcmdev_ctrl, 1);
|
|
|
|
if (ret)
|
|
|
|
dev_err(pcm_dev->dev, "%s: add_controls err = %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcmdevice_config_info_remove(void *ctxt)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *) ctxt;
|
|
|
|
struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
|
|
|
|
struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
if (!cfg_info)
|
|
|
|
return;
|
|
|
|
for (i = 0; i < regbin->ncfgs; i++) {
|
|
|
|
if (!cfg_info[i])
|
|
|
|
continue;
|
|
|
|
if (cfg_info[i]->blk_data) {
|
|
|
|
for (j = 0; j < (int)cfg_info[i]->real_nblocks; j++) {
|
|
|
|
if (!cfg_info[i]->blk_data[j])
|
|
|
|
continue;
|
|
|
|
kfree(cfg_info[i]->blk_data[j]->regdata);
|
|
|
|
kfree(cfg_info[i]->blk_data[j]);
|
|
|
|
}
|
|
|
|
kfree(cfg_info[i]->blk_data);
|
|
|
|
}
|
|
|
|
kfree(cfg_info[i]);
|
|
|
|
}
|
|
|
|
kfree(cfg_info);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_regbin_ready(const struct firmware *fmw, void *ctxt)
|
|
|
|
{
|
|
|
|
struct pcmdevice_config_info **cfg_info;
|
|
|
|
struct pcmdevice_priv *pcm_dev = ctxt;
|
|
|
|
struct pcmdevice_regbin_hdr *fw_hdr;
|
|
|
|
struct pcmdevice_regbin *regbin;
|
|
|
|
unsigned int total_config_sz = 0;
|
|
|
|
int offset = 0, ret = 0, i;
|
|
|
|
unsigned char *buf;
|
|
|
|
|
|
|
|
regbin = &(pcm_dev->regbin);
|
|
|
|
fw_hdr = &(regbin->fw_hdr);
|
|
|
|
if (!fmw || !fmw->data) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: failed to read %s\n",
|
|
|
|
__func__, pcm_dev->bin_name);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
buf = (unsigned char *)fmw->data;
|
|
|
|
|
|
|
|
fw_hdr->img_sz = get_unaligned_be32(&buf[offset]);
|
|
|
|
offset += 4;
|
|
|
|
if (fw_hdr->img_sz != fmw->size) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: file size(%d) not match %u",
|
|
|
|
__func__, (int)fmw->size, fw_hdr->img_sz);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
fw_hdr->checksum = get_unaligned_be32(&buf[offset]);
|
|
|
|
offset += 4;
|
|
|
|
fw_hdr->binary_version_num = get_unaligned_be32(&buf[offset]);
|
|
|
|
if (fw_hdr->binary_version_num < 0x103) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: bin version 0x%04x is out of date",
|
|
|
|
__func__, fw_hdr->binary_version_num);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
offset += 4;
|
|
|
|
fw_hdr->drv_fw_version = get_unaligned_be32(&buf[offset]);
|
|
|
|
offset += 8;
|
|
|
|
fw_hdr->plat_type = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
fw_hdr->dev_family = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
fw_hdr->reserve = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
fw_hdr->ndev = buf[offset];
|
|
|
|
offset += 1;
|
|
|
|
if (fw_hdr->ndev != pcm_dev->ndev) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: invalid ndev(%u)\n", __func__,
|
|
|
|
fw_hdr->ndev);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offset + PCMDEVICE_MAX_REGBIN_DEVICES > fw_hdr->img_sz) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: devs out of boundary!\n", __func__);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < PCMDEVICE_MAX_REGBIN_DEVICES; i++, offset++)
|
|
|
|
fw_hdr->devs[i] = buf[offset];
|
|
|
|
|
|
|
|
fw_hdr->nconfig = get_unaligned_be32(&buf[offset]);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
for (i = 0; i < PCMDEVICE_CONFIG_SUM; i++) {
|
|
|
|
fw_hdr->config_size[i] = get_unaligned_be32(&buf[offset]);
|
|
|
|
offset += 4;
|
|
|
|
total_config_sz += fw_hdr->config_size[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: bin file error!\n", __func__);
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL);
|
|
|
|
if (!cfg_info) {
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
regbin->cfg_info = cfg_info;
|
|
|
|
regbin->ncfgs = 0;
|
|
|
|
for (i = 0; i < (int)fw_hdr->nconfig; i++) {
|
|
|
|
cfg_info[i] = pcmdevice_add_config(ctxt, &buf[offset],
|
|
|
|
fw_hdr->config_size[i], &ret);
|
|
|
|
if (ret) {
|
|
|
|
/* In case the bin file is partially destroyed. */
|
|
|
|
if (regbin->ncfgs == 0)
|
|
|
|
pcm_dev->fw_state = PCMDEVICE_FW_LOAD_FAILED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
offset += (int)fw_hdr->config_size[i];
|
|
|
|
regbin->ncfgs += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) {
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: remove config due to fw load error!\n", __func__);
|
|
|
|
pcmdevice_config_info_remove(pcm_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_comp_probe(struct snd_soc_component *comp)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(comp);
|
|
|
|
struct i2c_adapter *adap = pcm_dev->client->adapter;
|
|
|
|
const struct firmware *fw_entry = NULL;
|
|
|
|
int ret, i, j;
|
|
|
|
|
|
|
|
mutex_lock(&pcm_dev->codec_lock);
|
|
|
|
|
|
|
|
pcm_dev->component = comp;
|
|
|
|
|
|
|
|
for (i = 0; i < pcm_dev->ndev; i++) {
|
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
|
ret = pcmdev_gain_ctrl_add(pcm_dev, i, j);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (comp->name_prefix) {
|
|
|
|
/* There's name_prefix defined in DTS. Bin file name will be
|
|
|
|
* name_prefix.bin stores the firmware including register
|
|
|
|
* setting and params for different filters inside chips, it
|
|
|
|
* must be copied into firmware folder. The same types of
|
|
|
|
* pcmdevices sitting on the same i2c bus will be aggregated as
|
|
|
|
* one single codec, all of them share the same bin file.
|
|
|
|
*/
|
|
|
|
scnprintf(pcm_dev->bin_name, PCMDEVICE_BIN_FILENAME_LEN,
|
|
|
|
"%s.bin", comp->name_prefix);
|
|
|
|
} else {
|
|
|
|
/* There's NO name_prefix defined in DTS. Bin file name will be
|
|
|
|
* device-name[defined in pcmdevice_i2c_id]-i2c-bus_id
|
|
|
|
* [0,1,...,N]-sum[1,...,4]dev.bin stores the firmware
|
|
|
|
* including register setting and params for different filters
|
|
|
|
* inside chips, it must be copied into firmware folder. The
|
|
|
|
* same types of pcmdevices sitting on the same i2c bus will be
|
|
|
|
* aggregated as one single codec, all of them share the same
|
|
|
|
* bin file.
|
|
|
|
*/
|
|
|
|
scnprintf(pcm_dev->bin_name, PCMDEVICE_BIN_FILENAME_LEN,
|
|
|
|
"%s-i2c-%d-%udev.bin", pcm_dev->dev_name, adap->nr,
|
|
|
|
pcm_dev->ndev);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = request_firmware(&fw_entry, pcm_dev->bin_name, pcm_dev->dev);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: request %s err = %d\n", __func__,
|
|
|
|
pcm_dev->bin_name, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pcmdev_regbin_ready(fw_entry, pcm_dev);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: %s parse err = %d\n", __func__,
|
|
|
|
pcm_dev->bin_name, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
ret = pcmdev_profile_ctrl_add(pcm_dev);
|
|
|
|
out:
|
|
|
|
if (fw_entry)
|
|
|
|
release_firmware(fw_entry);
|
|
|
|
|
|
|
|
mutex_unlock(&pcm_dev->codec_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void pcmdevice_comp_remove(struct snd_soc_component *codec)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
|
|
|
|
|
|
|
|
if (!pcm_dev)
|
|
|
|
return;
|
|
|
|
mutex_lock(&pcm_dev->codec_lock);
|
|
|
|
pcmdevice_config_info_remove(pcm_dev);
|
|
|
|
mutex_unlock(&pcm_dev->codec_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_widget pcmdevice_dapm_widgets[] = {
|
|
|
|
SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_AIF_OUT("ASI1 OUT", "ASI1 Capture",
|
|
|
|
0, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_OUTPUT("OUT"),
|
|
|
|
SND_SOC_DAPM_INPUT("MIC"),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_route pcmdevice_audio_map[] = {
|
|
|
|
{"OUT", NULL, "ASI"},
|
|
|
|
{"ASI1 OUT", NULL, "MIC"},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_component_driver
|
|
|
|
soc_codec_driver_pcmdevice = {
|
|
|
|
.probe = pcmdevice_comp_probe,
|
|
|
|
.remove = pcmdevice_comp_remove,
|
|
|
|
.dapm_widgets = pcmdevice_dapm_widgets,
|
|
|
|
.num_dapm_widgets = ARRAY_SIZE(pcmdevice_dapm_widgets),
|
|
|
|
.dapm_routes = pcmdevice_audio_map,
|
|
|
|
.num_dapm_routes = ARRAY_SIZE(pcmdevice_audio_map),
|
|
|
|
.suspend_bias_off = 1,
|
|
|
|
.idle_bias_on = 0,
|
|
|
|
.use_pmdown_time = 1,
|
|
|
|
.endianness = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pcmdev_single_byte_wr(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned char *data, int devn, int sublocksize)
|
|
|
|
{
|
|
|
|
unsigned short len = get_unaligned_be16(&data[2]);
|
|
|
|
int offset = 2;
|
|
|
|
int i, ret;
|
|
|
|
|
|
|
|
offset += 2;
|
|
|
|
if (offset + 4 * len > sublocksize) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d byt wr out of boundary\n",
|
|
|
|
__func__, devn);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
ret = pcmdev_dev_write(pcm_dev, devn,
|
|
|
|
PCMDEVICE_REG(data[offset + 1], data[offset + 2]),
|
|
|
|
data[offset + 3]);
|
|
|
|
/* skip this error for next operation or next devices */
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d single write err\n",
|
|
|
|
__func__, devn);
|
|
|
|
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_burst_wr(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned char *data, int devn, int sublocksize)
|
|
|
|
{
|
|
|
|
unsigned short len = get_unaligned_be16(&data[2]);
|
|
|
|
int offset = 2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
offset += 2;
|
|
|
|
if (offset + 4 + len > sublocksize) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d burst Out of boundary\n",
|
|
|
|
__func__, devn);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (len % 4) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d bst-len(%u) not div by 4\n",
|
|
|
|
__func__, devn, len);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
ret = pcmdev_dev_bulk_write(pcm_dev, devn,
|
|
|
|
PCMDEVICE_REG(data[offset + 1], data[offset + 2]),
|
|
|
|
&(data[offset + 4]), len);
|
|
|
|
/* skip this error for next devices */
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d bulk_write err = %d\n",
|
|
|
|
__func__, devn, ret);
|
|
|
|
|
|
|
|
offset += (len + 4);
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_delay(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned char *data, int devn, int sublocksize)
|
|
|
|
{
|
|
|
|
unsigned int delay_time = 0;
|
|
|
|
int offset = 2;
|
|
|
|
|
|
|
|
if (offset + 2 > sublocksize) {
|
2024-04-23 07:44:28 +00:00
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d delay out of boundary\n",
|
2024-04-07 09:18:42 +00:00
|
|
|
__func__, devn);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
delay_time = get_unaligned_be16(&data[2]) * 1000;
|
|
|
|
usleep_range(delay_time, delay_time + 50);
|
|
|
|
offset += 2;
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdev_bits_wr(struct pcmdevice_priv *pcm_dev,
|
|
|
|
unsigned char *data, int devn, int sublocksize)
|
|
|
|
{
|
|
|
|
int offset = 2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (offset + 6 > sublocksize) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d bit write out of memory\n",
|
|
|
|
__func__, devn);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
ret = pcmdev_dev_update_bits(pcm_dev, devn,
|
|
|
|
PCMDEVICE_REG(data[offset + 3], data[offset + 4]),
|
|
|
|
data[offset + 1], data[offset + 5]);
|
|
|
|
/* skip this error for next devices */
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(pcm_dev->dev, "%s: dev-%d update_bits err = %d\n",
|
|
|
|
__func__, devn, ret);
|
|
|
|
|
|
|
|
offset += 6;
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_process_block(void *ctxt, unsigned char *data,
|
|
|
|
unsigned char dev_idx, int sublocksize)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
|
|
|
|
int devn, dev_end, ret = 0;
|
|
|
|
unsigned char subblk_typ = data[1];
|
|
|
|
|
|
|
|
if (dev_idx) {
|
|
|
|
devn = dev_idx - 1;
|
|
|
|
dev_end = dev_idx;
|
|
|
|
} else {
|
|
|
|
devn = 0;
|
|
|
|
dev_end = pcm_dev->ndev;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* loop in case of several devices sharing the same sub-block */
|
|
|
|
for (; devn < dev_end; devn++) {
|
|
|
|
switch (subblk_typ) {
|
|
|
|
case PCMDEVICE_CMD_SING_W:
|
|
|
|
ret = pcmdev_single_byte_wr(pcm_dev, data, devn, sublocksize);
|
|
|
|
break;
|
|
|
|
case PCMDEVICE_CMD_BURST:
|
|
|
|
ret = pcmdev_burst_wr(pcm_dev, data, devn, sublocksize);
|
|
|
|
break;
|
|
|
|
case PCMDEVICE_CMD_DELAY:
|
|
|
|
ret = pcmdev_delay(pcm_dev, data, devn, sublocksize);
|
|
|
|
break;
|
|
|
|
case PCMDEVICE_CMD_FIELD_W:
|
|
|
|
ret = pcmdev_bits_wr(pcm_dev, data, devn, sublocksize);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* In case of sub-block error, break the loop for the rest of
|
|
|
|
* devices.
|
|
|
|
*/
|
|
|
|
if (ret < 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcmdevice_select_cfg_blk(void *ctxt, int conf_no,
|
|
|
|
unsigned char block_type)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = (struct pcmdevice_priv *)ctxt;
|
|
|
|
struct pcmdevice_regbin *regbin = &(pcm_dev->regbin);
|
|
|
|
struct pcmdevice_config_info **cfg_info = regbin->cfg_info;
|
|
|
|
struct pcmdevice_block_data **blk_data;
|
|
|
|
int j, k;
|
|
|
|
|
|
|
|
if (conf_no >= regbin->ncfgs || conf_no < 0 || NULL == cfg_info) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: conf_no should be less than %u\n",
|
|
|
|
__func__, regbin->ncfgs);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
blk_data = cfg_info[conf_no]->blk_data;
|
|
|
|
|
|
|
|
for (j = 0; j < (int)cfg_info[conf_no]->real_nblocks; j++) {
|
|
|
|
unsigned int length = 0, ret;
|
|
|
|
|
|
|
|
if (block_type > 5 || block_type < 2) {
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: block_type should be out of range\n",
|
|
|
|
__func__);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (block_type != blk_data[j]->block_type)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (k = 0; k < (int)blk_data[j]->n_subblks; k++) {
|
|
|
|
ret = pcmdevice_process_block(pcm_dev,
|
|
|
|
blk_data[j]->regdata + length,
|
|
|
|
blk_data[j]->dev_idx,
|
|
|
|
blk_data[j]->block_size - length);
|
|
|
|
length += ret;
|
|
|
|
if (blk_data[j]->block_size < length) {
|
|
|
|
dev_err(pcm_dev->dev,
|
|
|
|
"%s: %u %u out of boundary\n",
|
|
|
|
__func__, length,
|
|
|
|
blk_data[j]->block_size);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (length != blk_data[j]->block_size)
|
|
|
|
dev_err(pcm_dev->dev, "%s: %u %u size is not same\n",
|
|
|
|
__func__, length, blk_data[j]->block_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_mute(struct snd_soc_dai *dai, int mute, int stream)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *codec = dai->component;
|
|
|
|
struct pcmdevice_priv *pcm_dev = snd_soc_component_get_drvdata(codec);
|
|
|
|
unsigned char block_type;
|
|
|
|
|
|
|
|
if (pcm_dev->fw_state == PCMDEVICE_FW_LOAD_FAILED) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: bin file not loaded\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mute)
|
|
|
|
block_type = PCMDEVICE_BIN_BLK_PRE_SHUTDOWN;
|
|
|
|
else
|
|
|
|
block_type = PCMDEVICE_BIN_BLK_PRE_POWER_UP;
|
|
|
|
|
|
|
|
mutex_lock(&pcm_dev->codec_lock);
|
|
|
|
pcmdevice_select_cfg_blk(pcm_dev, pcm_dev->cur_conf, block_type);
|
|
|
|
mutex_unlock(&pcm_dev->codec_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = snd_soc_dai_get_drvdata(dai);
|
|
|
|
unsigned int fsrate;
|
|
|
|
unsigned int slot_width;
|
|
|
|
int bclk_rate;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
fsrate = params_rate(params);
|
|
|
|
switch (fsrate) {
|
|
|
|
case 48000:
|
|
|
|
break;
|
|
|
|
case 44100:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(pcm_dev->dev, "%s: incorrect sample rate = %u\n",
|
|
|
|
__func__, fsrate);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
slot_width = params_width(params);
|
|
|
|
switch (slot_width) {
|
|
|
|
case 16:
|
|
|
|
break;
|
|
|
|
case 20:
|
|
|
|
break;
|
|
|
|
case 24:
|
|
|
|
break;
|
|
|
|
case 32:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(pcm_dev->dev, "%s: incorrect slot width = %u\n",
|
|
|
|
__func__, slot_width);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
bclk_rate = snd_soc_params_to_bclk(params);
|
|
|
|
if (bclk_rate < 0) {
|
|
|
|
dev_err(pcm_dev->dev, "%s: incorrect bclk rate = %d\n",
|
|
|
|
__func__, bclk_rate);
|
|
|
|
ret = bclk_rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_soc_dai_ops pcmdevice_dai_ops = {
|
|
|
|
.mute_stream = pcmdevice_mute,
|
|
|
|
.hw_params = pcmdevice_hw_params,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct snd_soc_dai_driver pcmdevice_dai_driver[] = {
|
|
|
|
{
|
|
|
|
.name = "pcmdevice-codec",
|
|
|
|
.capture = {
|
|
|
|
.stream_name = "Capture",
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = PCMDEVICE_MAX_CHANNELS,
|
|
|
|
.rates = PCMDEVICE_RATES,
|
|
|
|
.formats = PCMDEVICE_FORMATS,
|
|
|
|
},
|
|
|
|
.playback = {
|
|
|
|
.stream_name = "Playback",
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = PCMDEVICE_MAX_CHANNELS,
|
|
|
|
.rates = PCMDEVICE_RATES,
|
|
|
|
.formats = PCMDEVICE_FORMATS,
|
|
|
|
},
|
|
|
|
.ops = &pcmdevice_dai_ops,
|
|
|
|
.symmetric_rate = 1,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static const struct of_device_id pcmdevice_of_match[] = {
|
|
|
|
{ .compatible = "ti,adc3120" },
|
|
|
|
{ .compatible = "ti,adc5120" },
|
|
|
|
{ .compatible = "ti,adc6120" },
|
|
|
|
{ .compatible = "ti,dix4192" },
|
|
|
|
{ .compatible = "ti,pcm1690" },
|
|
|
|
{ .compatible = "ti,pcm3120" },
|
|
|
|
{ .compatible = "ti,pcm3140" },
|
|
|
|
{ .compatible = "ti,pcm5120" },
|
|
|
|
{ .compatible = "ti,pcm5140" },
|
|
|
|
{ .compatible = "ti,pcm6120" },
|
|
|
|
{ .compatible = "ti,pcm6140" },
|
|
|
|
{ .compatible = "ti,pcm6240" },
|
|
|
|
{ .compatible = "ti,pcm6260" },
|
|
|
|
{ .compatible = "ti,pcm9211" },
|
|
|
|
{ .compatible = "ti,pcmd3140" },
|
|
|
|
{ .compatible = "ti,pcmd3180" },
|
|
|
|
{ .compatible = "ti,pcmd512x" },
|
|
|
|
{ .compatible = "ti,taa5212" },
|
|
|
|
{ .compatible = "ti,taa5412" },
|
|
|
|
{ .compatible = "ti,tad5212" },
|
|
|
|
{ .compatible = "ti,tad5412" },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, pcmdevice_of_match);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct regmap_range_cfg pcmdevice_ranges[] = {
|
|
|
|
{
|
|
|
|
.range_min = 0,
|
|
|
|
.range_max = 256 * 128,
|
|
|
|
.selector_reg = PCMDEVICE_PAGE_SELECT,
|
|
|
|
.selector_mask = 0xff,
|
|
|
|
.selector_shift = 0,
|
|
|
|
.window_start = 0,
|
|
|
|
.window_len = 128,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_config pcmdevice_i2c_regmap = {
|
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
.cache_type = REGCACHE_MAPLE,
|
|
|
|
.ranges = pcmdevice_ranges,
|
|
|
|
.num_ranges = ARRAY_SIZE(pcmdevice_ranges),
|
|
|
|
.max_register = 256 * 128,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev)
|
|
|
|
{
|
|
|
|
if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
|
|
|
|
gpio_free(pcm_dev->irq_info.gpio);
|
|
|
|
free_irq(pcm_dev->irq_info.nmb, pcm_dev);
|
|
|
|
}
|
|
|
|
mutex_destroy(&pcm_dev->codec_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *str_to_upper(char *str)
|
|
|
|
{
|
|
|
|
char *orig = str;
|
|
|
|
|
|
|
|
if (!str)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
while (*str) {
|
|
|
|
*str = toupper(*str);
|
|
|
|
str++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return orig;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pcmdevice_i2c_probe(struct i2c_client *i2c)
|
|
|
|
{
|
|
|
|
const struct i2c_device_id *id = i2c_match_id(pcmdevice_i2c_id, i2c);
|
|
|
|
struct pcmdevice_priv *pcm_dev;
|
|
|
|
struct device_node *np;
|
|
|
|
unsigned int dev_addrs[PCMDEVICE_MAX_I2C_DEVICES];
|
|
|
|
int ret = 0, i = 0, ndev = 0;
|
|
|
|
|
|
|
|
pcm_dev = devm_kzalloc(&i2c->dev, sizeof(*pcm_dev), GFP_KERNEL);
|
2024-06-17 02:09:54 +00:00
|
|
|
if (!pcm_dev)
|
|
|
|
return -ENOMEM;
|
2024-04-07 09:18:42 +00:00
|
|
|
|
|
|
|
pcm_dev->chip_id = (id != NULL) ? id->driver_data : 0;
|
|
|
|
|
|
|
|
pcm_dev->dev = &i2c->dev;
|
|
|
|
pcm_dev->client = i2c;
|
|
|
|
|
|
|
|
if (pcm_dev->chip_id >= MAX_DEVICE)
|
|
|
|
pcm_dev->chip_id = 0;
|
|
|
|
|
|
|
|
strscpy(pcm_dev->dev_name, pcmdevice_i2c_id[pcm_dev->chip_id].name,
|
|
|
|
sizeof(pcm_dev->dev_name));
|
|
|
|
|
|
|
|
strscpy(pcm_dev->upper_dev_name,
|
|
|
|
pcmdevice_i2c_id[pcm_dev->chip_id].name,
|
|
|
|
sizeof(pcm_dev->upper_dev_name));
|
|
|
|
|
|
|
|
str_to_upper(pcm_dev->upper_dev_name);
|
|
|
|
|
|
|
|
pcm_dev->regmap = devm_regmap_init_i2c(i2c, &pcmdevice_i2c_regmap);
|
|
|
|
if (IS_ERR(pcm_dev->regmap)) {
|
|
|
|
ret = PTR_ERR(pcm_dev->regmap);
|
|
|
|
dev_err(&i2c->dev, "%s: failed to allocate register map: %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
i2c_set_clientdata(i2c, pcm_dev);
|
|
|
|
mutex_init(&pcm_dev->codec_lock);
|
|
|
|
np = pcm_dev->dev->of_node;
|
2024-07-02 21:53:49 +00:00
|
|
|
|
|
|
|
if (IS_ENABLED(CONFIG_OF)) {
|
|
|
|
u64 addr;
|
|
|
|
|
|
|
|
for (i = 0; i < PCMDEVICE_MAX_I2C_DEVICES; i++) {
|
|
|
|
if (of_property_read_reg(np, i, &addr, NULL))
|
|
|
|
break;
|
|
|
|
dev_addrs[ndev++] = addr;
|
|
|
|
}
|
2024-04-07 09:18:42 +00:00
|
|
|
} else {
|
|
|
|
ndev = 1;
|
|
|
|
dev_addrs[0] = i2c->addr;
|
|
|
|
}
|
|
|
|
pcm_dev->irq_info.gpio = of_irq_get(np, 0);
|
|
|
|
|
|
|
|
for (i = 0; i < ndev; i++)
|
|
|
|
pcm_dev->addr[i] = dev_addrs[i];
|
|
|
|
|
|
|
|
pcm_dev->ndev = ndev;
|
|
|
|
|
|
|
|
pcm_dev->hw_rst = devm_gpiod_get_optional(&i2c->dev,
|
|
|
|
"reset-gpios", GPIOD_OUT_HIGH);
|
|
|
|
/* No reset GPIO, no side-effect */
|
|
|
|
if (IS_ERR(pcm_dev->hw_rst)) {
|
|
|
|
if (pcm_dev->chip_id == PCM9211 || pcm_dev->chip_id == PCM1690)
|
|
|
|
pcm9211_sw_rst(pcm_dev);
|
|
|
|
else
|
|
|
|
pcmdevice_sw_rst(pcm_dev);
|
|
|
|
} else {
|
|
|
|
gpiod_set_value_cansleep(pcm_dev->hw_rst, 0);
|
|
|
|
usleep_range(500, 1000);
|
|
|
|
gpiod_set_value_cansleep(pcm_dev->hw_rst, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pcm_dev->chip_id == PCM1690)
|
|
|
|
goto skip_interrupt;
|
|
|
|
if (gpio_is_valid(pcm_dev->irq_info.gpio)) {
|
|
|
|
dev_dbg(pcm_dev->dev, "irq-gpio = %d", pcm_dev->irq_info.gpio);
|
|
|
|
|
|
|
|
ret = gpio_request(pcm_dev->irq_info.gpio, "PCMDEV-IRQ");
|
|
|
|
if (!ret) {
|
|
|
|
int gpio = pcm_dev->irq_info.gpio;
|
|
|
|
|
|
|
|
gpio_direction_input(gpio);
|
|
|
|
pcm_dev->irq_info.nmb = gpio_to_irq(gpio);
|
|
|
|
|
|
|
|
} else
|
|
|
|
dev_err(pcm_dev->dev, "%s: GPIO %d request error\n",
|
|
|
|
__func__, pcm_dev->irq_info.gpio);
|
|
|
|
} else
|
|
|
|
dev_err(pcm_dev->dev, "Looking up irq-gpio failed %d\n",
|
|
|
|
pcm_dev->irq_info.gpio);
|
|
|
|
|
|
|
|
skip_interrupt:
|
|
|
|
ret = devm_snd_soc_register_component(&i2c->dev,
|
|
|
|
&soc_codec_driver_pcmdevice, pcmdevice_dai_driver,
|
|
|
|
ARRAY_SIZE(pcmdevice_dai_driver));
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(&i2c->dev, "probe register comp failed %d\n", ret);
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret < 0)
|
|
|
|
pcmdevice_remove(pcm_dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pcmdevice_i2c_remove(struct i2c_client *i2c)
|
|
|
|
{
|
|
|
|
struct pcmdevice_priv *pcm_dev = i2c_get_clientdata(i2c);
|
|
|
|
|
|
|
|
pcmdevice_remove(pcm_dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct i2c_driver pcmdevice_i2c_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "pcmdevice-codec",
|
|
|
|
.of_match_table = of_match_ptr(pcmdevice_of_match),
|
|
|
|
},
|
|
|
|
.probe = pcmdevice_i2c_probe,
|
|
|
|
.remove = pcmdevice_i2c_remove,
|
|
|
|
.id_table = pcmdevice_i2c_id,
|
|
|
|
};
|
|
|
|
module_i2c_driver(pcmdevice_i2c_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Shenghao Ding <shenghao-ding@ti.com>");
|
|
|
|
MODULE_DESCRIPTION("ASoC PCM6240 Family Audio ADC/DAC Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|