snd-hda-codec-cs8409/patch_cirrus_new84.h
Alexander Egorenkov d8c9001418 CS8409 TAS5764L support
Signed-off-by: Alexander Egorenkov <egorenar-dev@posteo.net>
2025-02-21 19:10:51 +01:00

1906 lines
70 KiB
C

// this sets the power state of the AFG node - ie node 0x1
// this calls hda_sync_power_state
//hda_set_power_state(codec, AC_PWRST_D0);
// this checks the node has reached the requested power state
//state = hda_sync_power_state(codec, nid, power_state);
//
// pigs need local definition as this is a static local function
/*
* wait until the state is reached, returns the current state
*/
static unsigned int hda_sync_power_state_8409(struct hda_codec *codec,
hda_nid_t nid,
unsigned int power_state)
{
unsigned long end_time = jiffies + msecs_to_jiffies(500);
unsigned int state, actual_state;
mycodec_info(codec, "hda_sync_power_state_8409 to 0x%04x\n",power_state);
for (;;) {
state = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_POWER_STATE, 0);
if (state & AC_PWRST_ERROR)
break;
actual_state = (state >> 4) & 0x0f;
if (actual_state == power_state)
break;
if (time_after_eq(jiffies, end_time))
break;
/* wait until the codec reachs to the target state */
msleep(1);
}
mycodec_info(codec, "hda_sync_power_state_8409 power state 0x%04x\n",state);
return state;
}
// pigs - need my own power state
// Apple seems to set node 0x01 - the AFG - primarily
// hda_set_power_state sets all nodes to the required power state
// so apparently node 0x01 does not have the power capability - but is powerable!!
// if we wish to use this for all nodes then need to check for this
static unsigned int hda_set_node_power_state_dbg(struct hda_codec *codec, hda_nid_t nid, unsigned int power_state, bool dbgflg)
{
unsigned int wcaps = get_wcaps(codec, nid);
unsigned int state = power_state;
//unsigned int current_state;
if (dbgflg) mycodec_info(codec, "hda_set_node_power_state nid 0x%02x power %d\n",nid,power_state);
state = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
if (!(state & AC_PWRST_ERROR)) {
if (state != power_state) {
if (nid == 0x01 || (wcaps & AC_WCAP_POWER)) {
if (nid != 0x01 && codec->power_filter) {
state = codec->power_filter(codec, nid, power_state);
// ah - this is for preventing a node from being turned off
// we are not in AC_PWRST_D3 but we are requesting AC_PWRST_D3
// (Im assuming we assume if not in AC_PWRST_D3 we are in AC_PWRST_D0
if (state != power_state && power_state == AC_PWRST_D3)
{}
else
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, power_state);
}
else
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, power_state);
state = hda_sync_power_state_8409(codec, nid, power_state);
}
else
dev_info(hda_codec_dev(codec), "hda_set_node_power_state no power cap!!\n");
}
}
else {
dev_info(hda_codec_dev(codec), "hda_set_node_power_state ERROR!! nid 0x%02x 0x%04x\n",nid, state);
}
if (dbgflg) mycodec_info(codec, "hda_set_node_power_state end power %d\n",state);
return state;
}
static unsigned int hda_set_node_power_state(struct hda_codec *codec, hda_nid_t nid, unsigned int power_state)
{
return hda_set_node_power_state_dbg(codec, nid, power_state, 0);
}
static unsigned int hda_set_node_power_state_simple(struct hda_codec *codec, hda_nid_t nid, unsigned int power_state)
{
unsigned int state = power_state;
//unsigned int current_state;
mycodec_info(codec, "hda_set_node_power_state_simple power %d\n",power_state);
state = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
if (!(state & AC_PWRST_ERROR)) {
if (state != power_state) {
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_POWER_STATE, power_state);
state = hda_sync_power_state_8409(codec, nid, power_state);
}
}
mycodec_info(codec, "hda_set_node_power_state_simple end power %d\n",state);
return state;
}
static void hda_check_power_state(struct hda_codec *codec, hda_nid_t nid, int flagint)
{
unsigned int state;
state = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
mycodec_info(codec, "hda_check_power_state nid 0x%02x power 0x%04x %d\n", nid, state, flagint);
}
// go with Apple way??
// this always does a get with index 0 initially and terminates with a set to 0 finally
static inline unsigned int cs_8409_vendor_coef_get(struct hda_codec *codec, unsigned int idx)
{
struct cs8409_apple_spec *spec = codec->spec;
unsigned int retval;
snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0,
AC_VERB_GET_COEF_INDEX, 0);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, idx);
retval = snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0,
AC_VERB_GET_PROC_COEF, 0);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, 0);
return retval;
}
static inline void cs_8409_vendor_coef_set(struct hda_codec *codec, unsigned int idx,
unsigned int coef)
{
struct cs8409_apple_spec *spec = codec->spec;
snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0,
AC_VERB_GET_COEF_INDEX, 0);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, idx);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_PROC_COEF, coef);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, 0);
// appears to return 0
}
static inline unsigned int cs_8409_vendor_coef_set_mask(struct hda_codec *codec, unsigned int idx,
unsigned int coef, unsigned int mask, unsigned int srcval, int srcidx)
{
// for the moment hackily add srcidx argument while debugging
struct cs8409_apple_spec *spec = codec->spec;
unsigned int retval;
unsigned int mask_coef;
snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0,
AC_VERB_GET_COEF_INDEX, 0);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, idx);
retval = snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0,
AC_VERB_GET_PROC_COEF, 0);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, idx);
mask_coef = (retval & ~mask) | coef;
if (srcval != 0)
{
if (srcidx != 0 && mask_coef != srcval)
myprintk_dbg("snd_hda_intel: cs_8409_vendor_coef_set_mask 0x%04x 0x%04x: 0x%04x (0x%04x 0x%04x 0x%04x) 0x%04x != 0x%04x %d BAD",idx,coef,mask_coef,retval,coef,mask, mask_coef, srcval, srcidx);
else
myprintk_dbg("snd_hda_intel: cs_8409_vendor_coef_set_mask 0x%04x 0x%04x: 0x%04x (0x%04x 0x%04x 0x%04x) %d",idx,coef,mask_coef,retval,coef,mask,srcidx);
}
else
//if (mask != 0xffff)
myprintk_dbg("snd_hda_intel: cs_8409_vendor_coef_set_mask 0x%04x 0x%04x: 0x%04x (0x%04x 0x%04x 0x%04x) %d",idx,coef,mask_coef,retval,coef,mask,srcidx);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_PROC_COEF, mask_coef);
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0,
AC_VERB_SET_COEF_INDEX, 0);
// appears to return 0
// lets return the read value for checking
return retval;
}
static inline void cs_8409_vendor_enableI2Cclock(struct hda_codec *codec, unsigned int flag)
{
unsigned int retval = 0;
unsigned int newval = 0;
// note that apple returns the status value with data value in returned parameter
// snd_hda_codec_read just returns value - not sure what happens about errors
// looks as tho its assumed -1 is not a valid return value
// ah yes - because max val is 16 bit quantity
retval = cs_8409_vendor_coef_get(codec, 0x0);
//if (retval == -1)
if (retval == -1)
return;
newval = retval;
if (flag)
newval |= 0x8;
else
newval = (retval & 0xfffffff7);
cs_8409_vendor_coef_set(codec, 0x0, newval);
}
// define i2cRead and i2cWrite functions
// following Apple
static unsigned int cs_8409_vendor_i2cRead(struct hda_codec *codec, unsigned int i2c_address,
unsigned int i2c_reg, unsigned int paged)
{
// AppleHDAFunctionGroupCS8409::_i2cRead(bool, unsigned short, unsigned short, unsigned int*)
// note that last argument is return data
unsigned int i2c_reg_data;
unsigned int retval;
int rdcnt;
myprintk_dbg("snd_hda_intel: i2cRead 0x%04x 0x%04x: %d",i2c_address,i2c_reg,paged);
hda_set_node_power_state_dbg(codec, codec->core.afg, AC_PWRST_D0, 0);
// exit on error
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_PROC_STATE, 0x00000001);
// exit on error
cs_8409_vendor_enableI2Cclock(codec, 0x1);
cs_8409_vendor_coef_set(codec, 0x59, i2c_address);
if (paged)
{
unsigned int retval1;
cs_8409_vendor_coef_set(codec, 0x5d, i2c_reg >> 8);
rdcnt = -8;
sleep1:
retval1 = cs_8409_vendor_coef_get(codec, 0x5c);
if (retval1 != -1)
{
retval1 &= 0x18;
if (retval1 != 0x18)
{
if (rdcnt < 0)
{
rdcnt++;
// need 0x2 according to Apple
usleep_range(2000,4000);
goto sleep1;
}
}
}
}
// so the i2c register is stored in the low byte of i2c_reg
// shift it 8 bits to left for sending as coefficient data (16 bits)
// hmm - why do I need a mask??
// think either we mask here or in cs_8409_vendor_coef_set
// Apple is using short ints so likely automasked
i2c_reg_data = (i2c_reg << 8) & 0x0ffff;
cs_8409_vendor_coef_set(codec, 0x5e, i2c_reg_data);
//if (retval == -1)
retval = cs_8409_vendor_coef_get(codec, 0x5c);
//if (retval == -1)
rdcnt = -8;
sleep2:
retval = cs_8409_vendor_coef_get(codec, 0x5c);
//if (retval == -1)
if (retval != -1)
{
retval &= 0x18;
if (retval != 0x18)
{
if (rdcnt < 0)
{
rdcnt++;
// need 0x2 according to Apple
usleep_range(2000,4000);
goto sleep2;
}
}
}
// well thats interesting - looks as though the 16 bit return
// has the register in bits 15-8 and the data in 7-0
// probably should mask the data out
retval = cs_8409_vendor_coef_get(codec, 0x5e);
//if (retval == -1)
cs_8409_vendor_enableI2Cclock(codec, 0x0);
// exit on error
myprintk_dbg("snd_hda_intel: i2cRead 0x%04x 0x%04x: 0x%04x end",i2c_address,i2c_reg,retval);
//hda_set_node_power_state(codec, codec->core.afg, AC_PWRST_D3);
// exit on error
return retval;
}
static unsigned int cs_8409_vendor_i2cWrite(struct hda_codec *codec, unsigned int i2c_address,
unsigned int i2c_reg, unsigned int i2c_data, unsigned int paged)
{
// AppleHDAFunctionGroupCS8409::_i2cWrite(bool, unsigned short, unsigned short, unsigned short)
unsigned int retval;
unsigned int i2c_reg_data;
int rdcnt;
myprintk_dbg("snd_hda_intel: i2cWrite 0x%04x 0x%04x: 0x%04x %d",i2c_address,i2c_reg,i2c_data,paged);
hda_set_node_power_state(codec, codec->core.afg, AC_PWRST_D0);
// exit on error
snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_PROC_STATE, 0x00000001);
// exit on error
cs_8409_vendor_enableI2Cclock(codec, 0x1);
cs_8409_vendor_coef_set(codec, 0x59, i2c_address);
if (paged)
{
unsigned int retval1;
cs_8409_vendor_coef_set(codec, 0x5d, i2c_reg >> 8);
retval1 = cs_8409_vendor_coef_get(codec, 0x5c);
rdcnt = -8;
sleep1:
retval1 = cs_8409_vendor_coef_get(codec, 0x5c);
if (retval1 != -1)
{
retval1 &= 0x18;
if (retval1 != 0x18)
{
if (rdcnt < 0)
{
rdcnt++;
// need 0x2 according to Apple
usleep_range(2000,4000);
goto sleep1;
}
}
}
}
// so the i2c register is stored in the low byte of i2c_reg
// shift it 8 bits to left for sending as coefficient data (16 bits)
// then or in the 8 byte data
// mask here or in cs_8409_vendor_coef_set?
i2c_reg_data = ((i2c_reg << 8) & 0x0ff00) | ( i2c_data & 0x0ff);
cs_8409_vendor_coef_set(codec, 0x5d, i2c_reg_data);
//if (retval == -1)
retval = cs_8409_vendor_coef_get(codec, 0x5c);
//if (retval == -1)
rdcnt = -8;
sleep2:
retval = cs_8409_vendor_coef_get(codec, 0x5c);
//if (retval == -1)
if (retval != -1)
{
retval &= 0x18;
if (retval != 0x18)
{
if (rdcnt < 0)
{
rdcnt++;
// need 0x2 according to Apple
usleep_range(2000,4000);
goto sleep2;
}
}
}
cs_8409_vendor_enableI2Cclock(codec, 0x0);
// exit on error
myprintk_dbg("snd_hda_intel: i2cWrite 0x%04x 0x%04x: 0x%04x %d end",i2c_address,i2c_reg,i2c_data,paged);
//hda_set_node_power_state(codec, codec->core.afg, AC_PWRST_D3);
// exit on error
return retval;
}
static unsigned int cs_8409_vendor_i2cWriteMask(struct hda_codec *codec, unsigned int i2c_address,
unsigned int i2c_reg, unsigned int i2c_mask, unsigned int i2c_data, unsigned int paged)
{
// masked version to emulate AppleHDATDMDevice::maskWriteReg(unsigned short, unsigned char, unsigned char)
unsigned int retval;
unsigned int mask_val;
retval = cs_8409_vendor_i2cRead(codec, i2c_address, i2c_reg, paged);
mask_val = (retval & ~i2c_mask);
mask_val |= (i2c_data & i2c_mask);
myprintk_dbg("snd_hda_intel: i2cWriteMask 0x%04x 0x%04x: 0x%04x (0x%04x 0x%04x 0x%04x) %d",i2c_address,i2c_reg,mask_val,retval,i2c_data,i2c_mask,paged);
retval = cs_8409_vendor_i2cWrite(codec, i2c_address, i2c_reg, mask_val, paged);
return retval;
}
// this seems to be how to do a list of verbs
// there is command to do a sequence of these
// snd_hda_sequence_write
static const struct hda_verb cs8409_init_verbs[] = {
//{0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
//{0x24, AC_VERB_SET_PROC_STATE, 0x01}, /* VPW: processing on */
{} /* terminator */
};
struct hda_coef {
u16 write;
hda_nid_t nid;
u32 idx;
u32 param;
u32 retdata;
int srcidx;
};
// new feature to do a sequence of coef read/writes
// (seems to be used a lot for cs8409)
// note that we ignore the return for gets for the moment!!
// ooh - new idea - save the logged return and check
static const struct hda_coef cs8409_init_coef[] = {
//{0, 0x01, idx, 0x00, retdata, 0}, read
//{1, 0x01, idx, param, dmydata, 0}, write
//{2, 0x01, idx, param, retdata, 0}, write mask
};
void snd_hda_coef_item(struct hda_codec *codec, u16 write_flag, hda_nid_t nid, u32 idx, u32 param, u32 retdata, int srcidx)
{
if (write_flag == 2)
{
// NOTA BENE - just for initial debugging differentiation - pass a mask of 0xffff for total overwrite
// use snd_hda_coef_item_masked for actual masked setup
unsigned int retreadval = cs_8409_vendor_coef_set_mask(codec, idx, param, 0xffff, 0, srcidx);
if (retreadval != retdata)
{
if (srcidx > 0)
codec_dbg(codec, "command BAD mask return value at %d: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",srcidx,retreadval,retdata,nid,idx,param);
//else
// codec_dbg(codec, "command BAD mask return value: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",retreadval,retdata,nid,idx,param);
}
}
else if (write_flag == 1)
cs_8409_vendor_coef_set(codec, idx, param);
else
{
unsigned int retval = cs_8409_vendor_coef_get(codec, idx);
if (retval != retdata)
{
if (srcidx > 0)
codec_dbg(codec, "command BAD return value at %d: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",srcidx,retval,retdata,nid,idx,param);
//else
// codec_dbg(codec, "command BAD return value: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",retval,retdata,nid,idx,param);
}
}
}
// just create a special routine if we wish to return the actual value for the moment
int snd_hda_coef_item_check(struct hda_codec *codec, u16 write_flag, hda_nid_t nid, u32 idx, u32 param, u32 retdata, int srcidx)
{
int retval = 0;
if (write_flag == 2)
codec_dbg(codec, "command BAD usage of snd_hda_coef_item_check %d\n", write_flag);
else if (write_flag == 1)
codec_dbg(codec, "command BAD usage of snd_hda_coef_item_check %d\n", write_flag);
else
{
unsigned int retval1 = cs_8409_vendor_coef_get(codec, idx);
if (retval1 != retdata)
{
if (srcidx > 0)
codec_dbg(codec, "command BAD return value at %d: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",srcidx,retval1,retdata,nid,idx,param);
//else
// codec_dbg(codec, "command BAD return value: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",retval1,retdata,nid,idx,param);
}
retval = retval1;
}
return retval;
}
void snd_hda_coef_item_masked(struct hda_codec *codec, u16 write_flag, hda_nid_t nid, u32 idx, u32 param, u32 mask, u32 retdata, u32 srcval, int srcidx)
{
//int retval = 0;
if (write_flag != 2)
codec_dbg(codec, "command BAD usage of snd_hda_coef_item_masked %d\n", write_flag);
else
{
unsigned int retreadval = cs_8409_vendor_coef_set_mask(codec, idx, param, mask, srcval, srcidx);
if (retreadval != retdata)
{
if (srcidx > 0)
codec_dbg(codec, "command BAD mask return value at %d: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",srcidx,retreadval,retdata,nid,idx,param);
//else
// codec_dbg(codec, "command BAD mask return value: 0x%08x 0x%08x (0x%02x, 0x%04x, 0x%04x)\n",retreadval,retdata,nid,idx,param);
}
}
//return retval;
}
void snd_hda_coef_sequence(struct hda_codec *codec, const struct hda_coef *seq, char *prtstr)
{
mycodec_info(codec, "start snd_hda_coef_sequence %s\n",prtstr);
for (; seq->nid; seq++)
{
snd_hda_coef_item(codec, seq->write, seq->nid, seq->idx, seq->param, seq->retdata, seq->srcidx);
}
mycodec_info(codec, "end snd_hda_coef_sequence %s\n",prtstr);
}
static inline unsigned int snd_hda_codec_read_check(struct hda_codec *codec, hda_nid_t nid, int flags, unsigned int verb, unsigned int parm, unsigned int check_val, int srcidx)
{
unsigned int retval;
retval = snd_hda_codec_read(codec, nid, flags, verb, parm);
if (retval == -1)
return retval;
if (srcidx > 0)
if (retval != check_val)
codec_dbg(codec, "command BAD read check return value at %d: 0x%08x 0x%08x (0x%02x, 0x%03x 0x%04x)\n",srcidx,retval,check_val,nid,verb,parm);
return retval;
}
void snd_hda_double_reset(struct hda_codec *codec)
{
mycodec_info(codec, "snd_hda_double_reset\n");
// still not clear if this does anything
snd_hda_codec_write(codec, codec->core.afg, 0, 0xfff, 0);
// so far the double reset seems to give bad results - lots of registers dont compare
//snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_CODEC_RESET, 0);
msleep(1);
// apparently should use usleep_range for a few ms
//usleep_range(1000,2000);
//snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_CODEC_RESET, 0);
}
static void clear_pins(struct hda_codec *codec)
{
//struct cs8409_apple_spec *spec = codec->spec;
hda_nid_t nid;
mycodec_info(codec, "start clear_pins\n");
for_each_hda_codec_node(nid, codec)
if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_PIN) {
/* use read here for syncing after issuing each verb */
snd_hda_codec_read(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
}
mycodec_info(codec, "end clear_pins\n");
}
static void read_coefs_all_loop(struct hda_codec *codec)
{
//struct cs8409_apple_spec *spec = codec->spec;
int idx;
mycodec_info(codec, "start read_coefs_all\n");
for (idx = 0; idx < 130; idx++)
{
int retval = cs_8409_vendor_coef_get(codec, idx);
mycodec_info(codec,"snd_hda_intel: read_coefs_all 0x%02x: 0x%08x\n",idx,retval);
}
mycodec_info(codec, "end read_coefs_all\n");
}
// this is very hacky but until get more understanding of what we can do with the 8409 setup
// re-define these from hda_codec.c here
// NOTA BENE - need to check this is consistent with any hda_codec.c updates!!
/*
* audio-converter setup caches
*/
struct hda_cvt_setup {
hda_nid_t nid;
u8 stream_tag;
u8 channel_id;
u16 format_id;
unsigned char active; /* cvt is currently used */
unsigned char dirty; /* setups should be cleared */
};
// we now setup our local cache data in the spec structure
// - cvt_setups is an opaque pointer type so we can see it here
// but we dont know how to access the data - except by re-defining hda_cvt_setup as above
/* get or create a cache entry for the given audio converter NID */
static struct hda_cvt_setup *
get_hda_cvt_setup_8409(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_cvt_setup *p;
int i;
snd_array_for_each(&codec->cvt_setups, i, p) {
if (p->nid == nid)
return p;
}
p = snd_array_new(&codec->cvt_setups);
if (p)
p->nid = nid;
return p;
}
// so we actually need both versions - one using the hda_cvt_setup struct
// and one using our local hda_cvt_setup_apple struct
static struct hda_cvt_setup_apple *
get_hda_cvt_setup_apple_8409(struct hda_codec *codec, hda_nid_t nid)
{
struct cs8409_apple_spec *spec = codec->spec;
switch (nid)
{
case 0x02:
return &spec->nid_0x02;
case 0x03:
return &spec->nid_0x03;
case 0x0a:
return &spec->nid_0x0a;
case 0x22:
return &spec->nid_0x22;
case 0x23:
return &spec->nid_0x23;
case 0x1a:
return &spec->nid_0x1a;
default:
break;
}
codec_err(codec, "get_hda_cvt_setup_apple_8409: UNKNOWN NID!! 0x%02x\n", nid);
return NULL;
}
static void cs_8409_dump_stream_format(struct hda_codec *codec, hda_nid_t nid)
{
struct hda_cvt_setup_apple *p = NULL;
int i;
// use explicit search so we dont create one if doesnt exist
for (i = 0; i < codec->cvt_setups.used; i++) {
p = snd_array_elem(&codec->cvt_setups, i);
if (p->nid == nid)
break;
}
if (p != NULL)
mycodec_dbg(codec, "cs_8409_dump_stream_format: NID=0x%x, codec cached values: stream=0x%x, channel=%d, format=0x%x\n", nid, p->stream_tag, p->channel_id, p->format_id);
else
mycodec_dbg(codec, "cs_8409_dump_stream_format: NID=0x%x, codec cached values: NULL\n", nid);
}
static void cs_8409_reset_stream_format(struct hda_codec *codec, hda_nid_t nid, int format, int doreset)
{
// note that this routine is currently not used
// this resets the cached stream format so that next
// stream setup will actually rewrite the stream format and stream id
// or if doreset set it will perform the stream update now
// also allow for only updating the stream format and not stream id
// NOTE we now save the stream format in our local cache as the hda_codec cache
// is cleared at end of the prepare stage and we want to store it more permanently
// really only the stream id is variable
struct hda_cvt_setup *p = NULL;
struct hda_cvt_setup_apple *papl = NULL;
u32 stream_tag_sv;
int channel_id_sv;
int format_id_sv;
// problem - the get_hda_cvt_setup function is local to hda_codec - so need our own copy above
papl = get_hda_cvt_setup_apple_8409(codec, nid);
stream_tag_sv = papl->stream_tag;
channel_id_sv = papl->channel_id;
format_id_sv = papl->format_id;
mycodec_info(codec, "cs_8409_reset_stream_format RESET for nid 0x%02x: 0x%08x id 0x%08x chan 0x%08x\n", nid, format_id_sv, stream_tag_sv, channel_id_sv);
// snd_hda_codec_setup_stream uses a caching system so only sends verbs when a change occurs
// we want to force a send here so need to clear the cached data
p = get_hda_cvt_setup_8409(codec, nid);
p->stream_tag = 0;
p->channel_id = 0;
if (format)
p->format_id = 0;
if (doreset)
snd_hda_codec_setup_stream(codec, nid, stream_tag_sv, channel_id_sv, format_id_sv);
}
// so what do I want this to do
// the stream format will be stored in the hda_cvt_setup (at what stage is this valid??)
// - we want to remove the Apple specific stream format/channel setup
// and just call snd_hda_setup_stream - but we need the actual stream format for this
// - hopefully getting from the hda_cvt_setup struct
// unfortunately this idea of storing in the hda_cvt_setup table turns out to be not useful
// as at end of snd_hda_codec_prepare it clears out (ie zeros) all unused/inactive cache entries
// so we have to store in a separate cache using our own copied definition for hda_cvt_setup
// hda_cvt_setup_apple
// the following 2 functions are used in the sync converter functions
// where apple essentially disables streaming (set stream id to 0) updates some vendor nid parameters
// then restores streaming
// so we store the stream info in a local variable copy and set it to the unused stream id ie stream id of 0
// then cs_8409_update_from_save_stream_format sets it back to what it was
// note that the format is unchanged for these operations
// the main reason for doing it this way is because of the caching used in snd_hda_codec_setup_stream
// - if we just sent the hda verbs then the cached data in snd_hda_codec_setup_stream
// would be inconsistent with the actual state of streaming on the nid
static void cs_8409_save_and_clear_stream_format(struct hda_codec *codec, hda_nid_t nid, struct hda_cvt_setup *savep)
{
struct hda_cvt_setup *p = NULL;
u32 stream_tag_sv;
int channel_id_sv;
int format_id_sv;
mycodec_dbg(codec, "cs_8409_save_and_clear_stream_format nid 0x%02x\n", nid);
// use this to save the stream format and clear the stream id and channel
p = get_hda_cvt_setup_8409(codec, nid);
savep->stream_tag = p->stream_tag;
savep->channel_id = p->channel_id;
savep->format_id = p->format_id;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0x00000000);
}
static void cs_8409_update_from_save_stream_format(struct hda_codec *codec, hda_nid_t nid, struct hda_cvt_setup *savep, int update_stream_id, int update_format_id)
{
struct hda_cvt_setup *p = NULL;
mycodec_dbg(codec, "cs_8409_update_from_save_stream_format nid 0x%02x\n", nid);
// so this will ensure the format is re-updated
p = get_hda_cvt_setup_8409(codec, nid);
if (update_stream_id)
{
p->stream_tag = 0;
p->channel_id = 0;
}
if (update_format_id)
p->format_id = 0;
mycodec_info(codec, "cs_8409_update_from_save_stream_format tag 0x%08x chnl 0x%08x fmt 0x%08x\n", savep->stream_tag, savep->channel_id, savep->format_id);
snd_hda_codec_setup_stream(codec, nid, savep->stream_tag, savep->channel_id, savep->format_id);
}
// so these are the crucial routines for setting our local cached copy of the stream info (in the spec structure)
// we use a different struct definition (hda_cvt_setup_apple) to keep the re-definition of hda_cvt_setup more local
// note that this stream info is only stored once per stream prepare function call
// and this routine always updates from that initial data
static void cs_8409_really_update_stream_format(struct hda_codec *codec, hda_nid_t nid, int update_format_id, int update_stream_id, unsigned int new_channel_id)
{
struct hda_cvt_setup *p = NULL;
struct hda_cvt_setup_apple *papl = NULL;
u32 stream_tag_sv = 0;
int channel_id_sv = 0;
int format_id_sv = 0;
mycodec_dbg(codec, "cs_8409_really_update_stream_format nid 0x%02x updfmt %d updstrmid %d nchnlid %d\n", nid, update_format_id, update_stream_id, new_channel_id);
//dump_stack();
// so here we take our local cached format and save locally, clear out the cached values
// then call snd_hda_codec_setup_stream with the cached values
// this will ensure we update the HDA with the stream format
// maybe now we should just update from our local stored version??
papl = get_hda_cvt_setup_apple_8409(codec, nid);
if (papl != NULL)
{
stream_tag_sv = papl->stream_tag;
channel_id_sv = papl->channel_id;
format_id_sv = papl->format_id;
}
else
{
codec_err(codec, "cs_8409_really_update_stream_format bad nid 0x%02x FAIL!!\n", nid);
return;
}
p = get_hda_cvt_setup_8409(codec, nid);
mycodec_info(codec, "cs_8409_really_update_stream_format cached tag 0x%08x chnl 0x%08x fmt 0x%08x\n", papl->stream_tag, papl->channel_id, papl->format_id);
if (update_stream_id)
{
p->stream_tag = 0;
p->channel_id = 0;
}
if (update_format_id)
p->format_id = 0;
mycodec_info(codec, "cs_8409_really_update_stream_format to update tag 0x%08x chnl 0x%08x fmt 0x%08x\n", p->stream_tag, p->channel_id, p->format_id);
if (update_stream_id == 2)
mycodec_info(codec, "cs_8409_really_update_stream_format new tag 0x%08x chnl 0x%08x fmt 0x%08x\n", stream_tag_sv, new_channel_id, format_id_sv);
else
mycodec_info(codec, "cs_8409_really_update_stream_format new tag 0x%08x chnl 0x%08x fmt 0x%08x\n", stream_tag_sv, channel_id_sv, format_id_sv);
cs_8409_dump_stream_format(codec, nid);
if (update_stream_id == 2)
snd_hda_codec_setup_stream(codec, nid, stream_tag_sv, new_channel_id, format_id_sv);
else
snd_hda_codec_setup_stream(codec, nid, stream_tag_sv, channel_id_sv, format_id_sv);
}
// remove function from compile so get error when building if use it
#if 0
static void cs_8409_setup_stream_format(struct hda_codec *codec, hda_nid_t nid, unsigned int stream_tag, unsigned int format)
{
struct hda_cvt_setup *p = NULL;
// NOTE - this function should no longer be used
mycodec_dbg(codec, "cs_8409_setup_stream_format nid 0x%02x\n",nid);
cs_8409_dump_stream_format(codec, nid);
// this functions sets up the cached stream - get_hda_cvt_setup_8409 creates a struct if not yet defined
// NOTA BENE we do not do the update here - we are relying that this will be done by a call to
// cs_8409_really_update_stream_format now we have set the format correctly
p = get_hda_cvt_setup_8409(codec, nid);
// NOTA BENE - we do not set the channel id here - this will be done by cs_8409_really_update_stream_format
p->stream_tag = stream_tag;
p->channel_id = 0;
p->format_id = format;
cs_8409_dump_stream_format(codec, nid);
mycodec_dbg(codec, "end cs_8409_setup_stream_format\n");
}
#endif
static void cs_8409_store_stream_format(struct hda_codec *codec, hda_nid_t nid, unsigned int stream_tag, unsigned int format)
{
struct hda_cvt_setup_apple *papl = NULL;
mycodec_dbg(codec, "cs_8409_store_stream_format nid 0x%02x\n",nid);
cs_8409_dump_stream_format(codec, nid);
// this functions sets up our local cached stream save store
// NOTA BENE we do not do the update here - we are relying that this will be done by a call to
// cs_8409_really_update_stream_format now we have set the format correctly
papl = get_hda_cvt_setup_apple_8409(codec, nid);
if (papl != NULL)
{
// NOTA BENE - we do not set the channel id here - this will be done by cs_8409_really_update_stream_format
papl->stream_tag = stream_tag;
papl->channel_id = 0;
papl->format_id = format;
mycodec_info(codec, "cs_8409_store_stream_format cached tag 0x%08x chnl 0x%08x fmt 0x%08x\n", papl->stream_tag, papl->channel_id, papl->format_id);
}
else
codec_err(codec, "cs_8409_store_stream_format bad nid 0x%02x FAIL!!\n", nid);
mycodec_dbg(codec, "end cs_8409_store_stream_format\n");
}
//int snd_hda_get_path_idx(struct hda_codec *codec, struct nid_path *path);
//static struct nid_path *get_input_path(struct hda_codec *codec, int adc_idx, int imux_idx)
// return snd_hda_get_path_from_idx(codec, spec->input_paths[imux_idx][adc_idx]);
// modified from init_input_src to switch the inputs on headset plugin/unplug events
static void switch_input_src(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
struct hda_input_mux *imux = &spec->input_mux;
struct nid_path *path;
int i, c, nums;
mycodec_dbg(codec, "switch_input_src enter\n");
nums = spec->num_adc_nids;
mycodec_dbg(codec, "switch_input_src num adc nids %d %d\n",nums,spec->dyn_adc_switch);
for (c = 0; c < nums; c++) {
mycodec_dbg(codec, "switch_input_src num_items %d\n",imux->num_items);
for (i = 0; i < imux->num_items; i++) {
//path = get_input_path(codec, c, i);
path = snd_hda_get_path_from_idx(codec, spec->input_paths[i][c]);
if (path) {
int in;
bool active = path->active;
mycodec_dbg(codec, "switch_input_src path active %d\n",active);
for (in = path->depth - 1; in >= 0; in--) {
hda_nid_t tnid = path->path[in];
mycodec_dbg(codec, "switch_input_src path nid %d: 0x%02x\n",in,tnid);
}
if (path->active) {
mycodec_dbg(codec, "switch_input_src path nid 0x%02x deactivate\n",path->path[1]);
snd_hda_activate_path(codec, path, false, false);
} else {
mycodec_dbg(codec, "switch_input_src path nid 0x%02x activate\n",path->path[1]);
snd_hda_activate_path(codec, path, true, false);
}
}
else {
mycodec_dbg(codec, "switch_input_src path NULL\n");
}
}
}
mycodec_dbg(codec, "switch_input_src exit\n");
}
static int read_gpio_status_check(struct hda_codec *codec);
#ifdef USE_DATA
#include "patch_cirrus_data84.h"
#include "patch_cirrus_plugin.h"
#include "patch_cirrus_headplay.h"
#include "patch_cirrus_unplug.h"
#include "patch_cirrus_plugin3.h"
#include "patch_cirrus_plugin23.h"
#include "patch_cirrus_mb141_data84.h"
#else
// error definitions
static void cs_8409_external_device_unsolicited_response_data(struct hda_codec *codec, unsigned int res)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
static void cs_8409_boot_setup_data(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
static void cs_8409_play_data(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
//static void cs_8409_play_real(struct hda_codec *codec);
static void cs_8409_playstop_data(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
//static void cs_8409_playstop_real(struct hda_codec *codec);
static void cs_8409_headplay_data(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
//static void cs_8409_headplay_real(struct hda_codec *codec);
static void cs_8409_headplaystop_data(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
//static void cs_8409_headplaystop_real(struct hda_codec *codec);
static void cs_8409_boot_setup_data_ssm3(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
static void cs_8409_play_data_ssm3(struct hda_codec *codec)
{
dev_err(hda_codec_dev(codec), "ERROR - to use data functions need to define USE_DATA\n");
}
#endif
#include "patch_cirrus_boot84.h"
#include "patch_cirrus_real84_i2c.h"
#include "patch_cirrus_real84.h"
// only needed if wish to test the version using the mb141 logs
// cs_8409_boot_setup_real now supposed to do both machines
//#include "patch_cirrus_mb141_real84.h"
// macbook pro subsystem ids
// 14,1 0x106b3300
// 14,2 0x106b3600
// 14,3 0x106b3900
// imac subsystem ids
// 18,1 0x106b0e00
// 18,2 0x106b0f00
// 18,3 0x106b1000
// 19,1 0x106b1000
static int cs_8409_data_config(struct hda_codec *codec);
static int cs_8409_real_config(struct hda_codec *codec);
static int cs_8409_boot_setup(struct hda_codec *codec)
{
int err = 0;
struct cs8409_apple_spec *spec = codec->spec;
// so it appears we break up the subsystem_id into 2 parts
// a codec vendor id (16 bits) and a subvendor id (8 bits) plus an assembly id
// so here the codec vendor is 0x106b, the subvendor id is 0x39 and the assembly id is 0x00
if (codec->core.subsystem_id == 0x106b3900) {
if (spec->use_data) {
myprintk("snd_hda_intel: cs_8409_boot_setup pre cs_8409_data_config\n");
err = cs_8409_data_config(codec);
myprintk("snd_hda_intel: cs_8409_boot_setup post cs_8409_data_config\n");
} else {
myprintk("snd_hda_intel: cs_8409_boot_setup pre cs_8409_real_config\n");
err = cs_8409_real_config(codec);
myprintk("snd_hda_intel: cs_8409_boot_setup post cs_8409_real_config\n");
}
}
else if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600) {
if (spec->use_data) {
cs_8409_boot_setup_data_ssm3(codec);
} else {
myprintk("snd_hda_intel: cs_8409_boot_setup pre cs_8409_real_config\n");
//cs_8409_boot_setup_real_ssm3(codec);
err = cs_8409_real_config(codec);
}
}
else if (codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
printk("snd_hda_intel: cs_8409_boot_setup pre data not implemented for subsystem id 0x%08x",codec->core.subsystem_id);
} else {
err = cs_8409_real_config(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
err = -1;
}
return err;
}
void cs_8409_play_setup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
myprintk_dbg("snd_hda_intel: cs_8409_play_setup\n");
if (codec->core.subsystem_id == 0x106b3900) {
if (spec->use_data) {
//cs_8409_unmute_data(codec);
//cs_8409_volup_data(codec);
cs_8409_play_data(codec);
} else {
cs_8409_play_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600) {
if (spec->use_data) {
cs_8409_play_data_ssm3(codec);
} else {
//cs_8409_play_real_ssm3(codec);
cs_8409_play_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
printk("snd_hda_intel: cs_8409_play_setup data not implemented for subsystem id 0x%08x",codec->core.subsystem_id);
} else {
cs_8409_play_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
}
//static void cs_8409_playstop_data_ssm3(struct hda_codec *codec);
//static void cs_8409_playstop_real_ssm3(struct hda_codec *codec);
void cs_8409_play_cleanup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
myprintk_dbg("snd_hda_intel: cs_8409_play_cleanup\n");
if (codec->core.subsystem_id == 0x106b3900) {
if (spec->use_data) {
cs_8409_playstop_data(codec);
} else {
cs_8409_playstop_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600) {
if (spec->use_data) {
//cs_8409_playstop_data_ssm3(codec);
} else {
//cs_8409_playstop_real_ssm3(codec);
cs_8409_playstop_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
printk("snd_hda_intel: cs_8409_play_cleanup data not implemented for subsystem id 0x%08x",codec->core.subsystem_id);
} else {
cs_8409_playstop_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
}
// NOTE - so far all systems use the same inputs for internal mike capturing - not sure if
// there are any subsystem_id differences
void cs_8409_capture_setup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600 || codec->core.subsystem_id == 0x106b3900
|| codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
//cs_8409_capture_data(codec);
} else {
//cs_8409_capture_real(codec);
cs_8409_capture_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
}
void cs_8409_capture_cleanup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600 || codec->core.subsystem_id == 0x106b3900
|| codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
//cs_8409_capturestop_data(codec);
} else {
//cs_8409_capturestop_real(codec);
cs_8409_capturestop_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
}
static void cs_8409_cs42l83_unsolicited_response_finalize(struct hda_codec *codec, unsigned int res);
static void cs_8409_perform_external_device_unsolicited_responses(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
struct unsol_item *unsol_entry = NULL;
struct unsol_item *unsol_temp = NULL;
if (!list_empty(&spec->unsol_list)) {
codec_info(codec, "cs_8409_perform_external_device_unsolicited_responses UNSOL start\n");
list_for_each_entry_safe(unsol_entry, unsol_temp, &spec->unsol_list, list)
{
list_del_init(&unsol_entry->list);
// pigs this gets complicated - these might issue other unsol responses
cs_8409_cs42l83_unsolicited_response_finalize(codec, unsol_entry->res);
spec->unsol_items_prealloc_used[unsol_entry->idx] = 0;
memset(unsol_entry, 0, sizeof(struct unsol_item));
}
codec_info(codec, "cs_8409_perform_external_device_unsolicited_responses UNSOL end\n");
}
}
static void cs_8409_cs42l83_unsolicited_response(struct hda_codec *codec, unsigned int res)
{
struct cs8409_apple_spec *spec = codec->spec;
// not clear if want to use the GPIO pins apparently passed in res to determine
// if want to do interrupt checking here and if no interrupts then to do
// some other unsolicited response function (not seen any such unsolicited responses yet)
// without checking the unsolicited block status
// now think dont need a list - we can only have 1 outstanding unsolicted interrupt request
// we may get multiple unsolicited interrupt requests - but they all will have same GPIO status (0x26)
// and we determine the exact interrupt by reading the cs42l83 registers - which we are trying to avoid
// clashing with other verbs
// it may be that we get multiple interrupt flags to handle when we do read - not seen so far
if (spec->block_unsol)
{
int itm;
int new_itm = -1;
codec_info(codec, "cs_8409_cs42l83_unsolicited_response - UNSOL BLOCKED\n");
for (itm=0; itm<10; itm++)
if (spec->unsol_items_prealloc_used[itm] == 0) { new_itm = itm; break; }
if (new_itm < 0)
{
codec_info(codec, "cs_8409_cs42l83_unsolicited_response - IGNORING UNSOL RESPONSE!!\n");
return;
}
spec->unsol_items_prealloc_used[new_itm] = 1;
memset(&(spec->unsol_items_prealloc[new_itm]), 0, sizeof(struct unsol_item));
spec->unsol_items_prealloc[new_itm].res = res;
spec->unsol_items_prealloc[new_itm].idx = new_itm;
list_add_tail(&(spec->unsol_items_prealloc[new_itm].list), &spec->unsol_list);
codec_info(codec, "cs_8409_cs42l83_unsolicited_response - UNSOL response stored\n");
return;
}
else
codec_info(codec, "cs_8409_cs42l83_unsolicited_response - NOT UNSOL BLOCKED\n");
// so it appears we need to block unsol responses while doing unsol responses
// this is probably not the way to do this but still havent figured out how to use locking properly
// as this needs to be interruptible because some of these functions take a long time
// I think if we get here we cannot have been blocked so list maybe always empty
// whats not clear is if list_for_each_entry_safe is safe for addition also
spec->block_unsol = 1;
cs_8409_cs42l83_unsolicited_response_finalize(codec, res);
if (!list_empty(&spec->unsol_list))
{
mycodec_info(codec, "cs_8409_cs42l83_unsolicited_response - performing blocked responses start\n");
cs_8409_perform_external_device_unsolicited_responses(codec);
mycodec_info(codec, "cs_8409_cs42l83_unsolicited_response - performing blocked responses end\n");
}
spec->block_unsol = 0;
}
static void cs_8409_cs42l83_unsolicited_response_finalize(struct hda_codec *codec, unsigned int res)
{
struct cs8409_apple_spec *spec = codec->spec;
if (spec->use_data)
cs_8409_external_device_unsolicited_response_data(codec, res);
else
{
if (spec->headset_phase == 0)
{
mycodec_info(codec, "cs_8409_external_device_unsolicited_response_finalize - phase is 0 - skipping\n");
return;
}
// note the data version will only play thro the headphones for a single time
//cs_8409_external_device_unsolicited_response_data(codec, res);
cs_8409_external_device_unsolicited_response(codec);
}
}
static void cs_8409_headset_mike_setup_nouse(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
cs_8409_intmike_linein_disable(codec);
cs_8409_headset_mike_streaming_preplay(codec, 1);
cs_8409_headset_mike_buttons_enable(codec);
}
void cs_8409_headplay_setup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3900) {
if (spec->use_data) {
cs_8409_headplay_data(codec);
} else {
cs_8409_headplay_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600) {
if (spec->use_data) {
//cs_8409_play_data_ssm3(codec);
} else {
//cs_8409_play_real_ssm3(codec);
cs_8409_headplay_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
printk("snd_hda_intel: cs_8409_headplay_setup data not implemented for subsystem id 0x%08x",codec->core.subsystem_id);
} else {
cs_8409_headplay_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
// decided this needs moving till all stream setup verbs done
//spec->block_unsol = 0;
//if (!list_empty(&spec->unsol_list))
//{
// codec_info(codec, "cs_8409_headplay_setup - performing UNSOL responses\n");
// cs_8409_perform_external_device_unsolicited_responses(codec);
//}
}
void cs_8409_headplay_cleanup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3900) {
if (spec->use_data) {
cs_8409_headplaystop_data(codec);
} else {
//cs_8409_headplaystop_data(codec);
cs_8409_headplaystop_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600) {
if (spec->use_data) {
//cs_8409_play_data_ssm3(codec);
} else {
//cs_8409_play_real_ssm3(codec);
cs_8409_headplaystop_real(codec);
}
}
else if (codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
printk("snd_hda_intel: cs_8409_headplay_cleanup data not implemented for subsystem id 0x%08x",codec->core.subsystem_id);
} else {
cs_8409_headplaystop_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
// decided this needs moving till all stream cleanup verbs done
//spec->block_unsol = 0;
//if (!list_empty(&spec->unsol_list))
//{
// codec_info(codec, "cs_8409_headplay_cleanup - performing UNSOL responses\n");
// cs_8409_perform_external_device_unsolicited_responses(codec);
//}
}
// NOTE - so far all systems use the same chip (cs42l83) for headset mike capturing - not sure if
// there are any subsystem_id differences
void cs_8409_headcapture_setup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600 || codec->core.subsystem_id == 0x106b3900
|| codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
//cs_8409_headcapture_data(codec);
} else {
cs_8409_headcapture_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
// decided this needs moving till all stream setup verbs done
//spec->block_unsol = 0;
//if (!list_empty(&spec->unsol_list))
//{
// codec_info(codec, "cs_8409_headcapture_setup - performing UNSOL responses\n");
// cs_8409_perform_external_device_unsolicited_responses(codec);
//}
}
void cs_8409_headcapture_cleanup(struct hda_codec *codec)
{
struct cs8409_apple_spec *spec = codec->spec;
if (codec->core.subsystem_id == 0x106b3300 || codec->core.subsystem_id == 0x106b3600 || codec->core.subsystem_id == 0x106b3900
|| codec->core.subsystem_id == 0x106b1000 || codec->core.subsystem_id == 0x106b0f00 || codec->core.subsystem_id == 0x106b0e00) {
if (spec->use_data) {
//cs_8409_capturestop_data(codec);
} else {
//cs_8409_capturestop_real(codec);
cs_8409_headcapturestop_real(codec);
}
}
else {
printk("snd_hda_intel: UNKNOWN subsystem id 0x%08x",codec->core.subsystem_id);
}
// decided this needs moving till all stream cleanup verbs done
//spec->block_unsol = 0;
//if (!list_empty(&spec->unsol_list))
//{
// codec_info(codec, "cs_8409_headcapturestop_cleanup - performing UNSOL responses\n");
// cs_8409_perform_external_device_unsolicited_responses(codec);
//}
}
static void cs_8409_pcm_playback_pre_prepare_hook(struct hda_pcm_stream *hinfo, struct hda_codec *codec,
unsigned int stream_tag, unsigned int format, struct snd_pcm_substream *substream,
int action)
{
struct cs8409_apple_spec *spec = codec->spec;
if (action == HDA_GEN_PCM_ACT_PREPARE) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
struct timespec64 curtim;
ktime_get_real_ts64(&curtim);
#else
struct timespec curtim;
getnstimeofday(&curtim);
#endif
myprintk("snd_hda_intel: command cs_8409_pcm_playback_pre_prepare_hook HOOK PREPARE init %d last %lld cur %lld",spec->play_init,spec->last_play_time.tv_sec,curtim.tv_sec);
if (1) {
struct hda_cvt_setup_apple *p = NULL;
//int power_chk = 0;
// in the new way we set the stream up here using the passed data
// - this does not actually update the stream format here but sets the cached parameters
// so the cs_8409_really_update_stream_format will cause the updates to occur
// note we explicitly set the channel id - dont see another way yet
//cs_8409_setup_stream_format(codec, 0x02, stream_tag, format);
cs_8409_store_stream_format(codec, 0x02, stream_tag, format);
//cs_8409_setup_stream_format(codec, 0x03, stream_tag, format);
cs_8409_store_stream_format(codec, 0x03, stream_tag, format);
//cs_8409_setup_stream_format(codec, 0x0a, stream_tag, format);
cs_8409_store_stream_format(codec, 0x0a, stream_tag, format);
// save number of actual stream channels
spec->stream_channels = substream->runtime->channels;
hda_check_power_state(codec, 0x1a, 1);
hda_check_power_state(codec, 0x3c, 1);
// for the moment have junky test here
if (spec->jack_present)
{
// for the moment I think this works for both MB 14,1 and 14,3 - same hda and headphone chip
// note that so far only the headphone chip seems to generate unsol responses usually
spec->block_unsol = 1;
// we need to split this to deal with capture only setup
// and capture with play setup
// note this does mean we setup the mike in a different order to OSX
// if we are capturing with playing - because the capture setup seems to be done
// first on Linux and we dont know at that stage if we will be playing
if (spec->have_mike)
{
// actually we always need to do cs_8409_headplay_setup - here we are about to play
// - what this possibly would allow is the apple way of doing a pre-setup
// so here we would switch between doing a full setup or a partial setup
if (spec->headset_play_format_setup_needed)
{
cs_8409_headplay_setup(codec);
spec->headset_play_format_setup_needed = 0;
}
// we only setup capturing if we are actually doing capturing
//if (spec->headset_capture_format_setup_needed)
//{
// cs_8409_headcapture_setup(codec);
// spec->headset_capture_format_setup_needed = 0;
//}
}
else
{
cs_8409_headplay_setup(codec);
}
}
else {
cs_8409_play_setup(codec);
}
myprintk("snd_hda_intel: command cs_8409_pcm_playback_pre_prepare_hook setup play called");
hda_check_power_state(codec, 0x1a, 2);
hda_check_power_state(codec, 0x3c, 2);
// I dont now understand how this worked - the codes above ALWAYS reset the stream format
// to the OSX format
// and unless I force a stream update here there will be a stream format difference
// yet it appears it worked - even tho sometimes there was no format update after this routine
// now I dont know why
// so we need to force the stream to be re-set here
// problem is it appears hda_codec caches the stream format and id and only updates if changed
// and there doesnt seem to be a good way to force an update
// this routine doesnt seem to be nid specific - so explicitly fix the known nids here
// no longer needed now we set the stream format correctly above
// so when snd_hda_multi_out_analog_prepare is called after this routine it should do nothing
// as we will have cached and set the right format now
//cs_8409_reset_stream_format(codec, 0x02, 1, 0);
//cs_8409_reset_stream_format(codec, 0x03, 1, 0);
//cs_8409_reset_stream_format(codec, 0x0a, 1, 0);
spec->playing = 0;
spec->play_init = 1;
}
}
}
static void cs_8409_playback_pcm_hook(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream, int action)
{
struct cs8409_apple_spec *spec = codec->spec;
// so finally getting a handle on ordering here
// we need to do the OSX setup in the OPEN section
// as the generic hda format and stream setup is done BEFORE the PREPARE hook
// (theres a good chance we only need to do this once at least as long as machine doesnt sleep)
// (or we could just override the prepare function completely)
// I now think the noise was caused by mis-match between the stream format and the nid setup format
// (because the generic setup was done before the OSX setup and the actual streamed format is slightly different)
// (the hda documentation says these really need to match)
// It appears the 8409 setup can handle at least some differences in the stream format
// as long as we set the nid to format the kernel is sending
// certainly seems to handle S24_LE or S32_LE differences (OSX format is always S24_3LE)
if (action == HDA_GEN_PCM_ACT_OPEN) {
//struct hda_cvt_setup_apple *p = NULL;
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook open");
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook open end");
} else if (action == HDA_GEN_PCM_ACT_PREPARE) {
// so this comes AFTER the stream format, frequency setup verbs are sent for the pcm stream
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
struct timespec64 curtim;
ktime_get_real_ts64(&curtim);
#else
struct timespec curtim;
getnstimeofday(&curtim);
#endif
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook HOOK PREPARE init %d last %lld cur %lld",spec->play_init,spec->last_play_time.tv_sec,curtim.tv_sec);
//int power_chk = 0;
//power_chk = snd_hda_codec_read(codec, codec->core.afg, 0, AC_VERB_GET_POWER_STATE, 0);
//myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook power check 0x01 2 %d", power_chk);
// this is where we need to finally unset the block_unsol
// - which also means this is where we should check for unsolicited responses
spec->block_unsol = 0;
if (!list_empty(&spec->unsol_list))
{
codec_info(codec, "cs_8409_playback_pcm_hook - performing UNSOL responses\n");
cs_8409_perform_external_device_unsolicited_responses(codec);
}
spec->playing = 1;
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook HOOK PREPARE end");
} else if (action == HDA_GEN_PCM_ACT_CLEANUP) {
// so this also comes AFTER the stream format, frequency cleanup verbs are sent for the pcm stream
int power_chk = 0;
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook HOOK CLEANUP");
power_chk = snd_hda_codec_read(codec, codec->core.afg, 0, AC_VERB_GET_POWER_STATE, 0);
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook power check 0x01 3 %d", power_chk);
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook jack_present %d\n",spec->jack_present);
// for the moment have junky test here
if (spec->jack_present)
{
// for the moment I think this works for both MB 14,1 and 14,3 - same hda and headphone chip
// note that so far only the headphone chip seems to generate unsol responses usually
spec->block_unsol = 1;
// so dont think need to anything about capturing here
cs_8409_headplay_cleanup(codec);
spec->headset_play_format_setup_needed = 1;
}
else
cs_8409_play_cleanup(codec);
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook done play down");
spec->block_unsol = 0;
if (!list_empty(&spec->unsol_list))
{
codec_info(codec, "cs_8409_playback_pcm_hook - performing UNSOL responses\n");
cs_8409_perform_external_device_unsolicited_responses(codec);
}
// not sure of this position yet
spec->playing = 0;
power_chk = snd_hda_codec_read(codec, codec->core.afg, 0, AC_VERB_GET_POWER_STATE, 0);
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook power check 0x01 4 %d", power_chk);
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook HOOK CLEANUP end");
} else if (action == HDA_GEN_PCM_ACT_CLOSE) {
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook close");
myprintk("snd_hda_intel: command cs_8409_playback_pcm_hook close end");
}
}
static void cs_8409_pcm_capture_pre_prepare_hook(struct hda_pcm_stream *hinfo, struct hda_codec *codec,
unsigned int stream_tag, unsigned int format, struct snd_pcm_substream *substream,
int action)
{
struct cs8409_apple_spec *spec = codec->spec;
if (action == HDA_GEN_PCM_ACT_PREPARE) {
myprintk("snd_hda_intel: command cs_8409_pcm_capture_pre_prepare_hook HOOK PREPARE init %d",spec->capture_init);
// so the first action for internal mike recording (via Quicktime)
// is a headphone sense
// followed by amp setup for playing - is this just a feature of Quicktime??
// maybe Quicktime just auto sets up play just in case
// we dont seem to have a headphone sense if we have already plugged in the headset
// not that we can do anything - except abort if no headset plugged in??
//if (hinfo->nid == 0x22)
//{
// int retval;
// retval = cs42l83_headphone_sense(codec);
//}
// I think this is the same for intmike or headset mike
//cs_8409_setup_stream_format(codec, hinfo->nid, stream_tag, format);
cs_8409_store_stream_format(codec, hinfo->nid, stream_tag, format);
// for the moment have junky test here
if (spec->jack_present) {
spec->block_unsol = 1;
if (spec->have_mike)
{
// so it seems if we have a headset mike we always enable the
// headphones even if just capturing
if (spec->headset_play_format_setup_needed)
{
cs_8409_headplay_setup(codec);
spec->headset_play_format_setup_needed = 0;
}
if (spec->headset_capture_format_setup_needed)
{
cs_8409_headcapture_setup(codec);
spec->headset_capture_format_setup_needed = 0;
}
}
// I think this is impossible - this would say we tried to capture
// using a headset without mike
// NOTE - still not fixed linein/lineout working - this may need
// changing here
}
else
cs_8409_capture_setup(codec);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook setup capture called");
spec->capturing = 0;
spec->capture_init = 1;
}
}
static void cs_8409_capture_pcm_hook(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream, int action)
{
//struct cs8409_apple_spec *spec = codec->spec;
struct cs8409_apple_spec *spec = NULL;
myprintk_dbg("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK init called");
//dump_stack();
myprintk_dbg("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK init post stack");
// - so this seems to be the critical issue - this can apparently be called with a NULL codec!!!
// only thing to do seems to be to return!!
if (codec == NULL) {
struct hda_codec *badptr = NULL;
printk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK init - CODEC NULL");
// so if we are here it looks as tho we have been called from call_hp_automute
// - in which the codec is the 1st arg
badptr = (struct hda_codec *) hinfo;
spec = badptr->spec;
printk("snd_hda_intel: cs_8409_capture_pcm_hook - pcm_playback_hook %p", spec->gen.pcm_playback_hook);
printk("snd_hda_intel: cs_8409_capture_pcm_hook - pcm_capture_hook %p", spec->gen.pcm_capture_hook);
printk("snd_hda_intel: cs_8409_capture_pcm_hook - hp_automute_hook %p", spec->gen.hp_automute_hook);
printk("snd_hda_intel: cs_8409_capture_pcm_hook - line_automute_hook %p", spec->gen.line_automute_hook);
printk("snd_hda_intel: cs_8409_capture_pcm_hook - line_automute_hook %p", spec->gen.mic_autoswitch_hook);
printk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK init - CODEC NULL exit");
return;
}
else
myprintk_dbg("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK init - CODEC NOT NULL");
//dump_stack();
spec = codec->spec;
// so now no setup is done here - we only check for unsolicited responses
// - we do do cleanup for the CLEANUP action
if (action == HDA_GEN_PCM_ACT_OPEN) {
//struct hda_cvt_setup_apple *p = NULL;
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook open");
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook open end");
} else if (action == HDA_GEN_PCM_ACT_PREPARE) {
// so this comes AFTER the stream format, frequency setup verbs are sent for the pcm stream
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK PREPARE init %d",spec->capture_init);
// this is where we need to finally unset the block_unsol
// - which also means this is where we should check for unsolicited responses
spec->block_unsol = 0;
if (!list_empty(&spec->unsol_list))
{
codec_info(codec, "cs_8409_capture_pcm_hook - performing UNSOL responses\n");
cs_8409_perform_external_device_unsolicited_responses(codec);
}
spec->capturing = 1;
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK PREPARE end");
} else if (action == HDA_GEN_PCM_ACT_CLEANUP) {
// so this also comes AFTER the stream format, frequency cleanup verbs are sent for the pcm stream
int power_chk = 0;
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK CLEANUP");
power_chk = snd_hda_codec_read(codec, codec->core.afg, 0, AC_VERB_GET_POWER_STATE, 0);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook power check 0x01 3 %d", power_chk);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook jack_present %d\n",spec->jack_present);
// for the moment have junky test here
if (spec->jack_present)
{
// for the moment I think this works for both MB 14,1 and 14,3 - same hda and headphone chip
// note that so far only the headphone chip seems to generate unsol responses usually
spec->block_unsol = 1;
if (spec->headset_capture_format_setup_needed == 0)
{
cs_8409_headcapture_cleanup(codec);
spec->headset_capture_format_setup_needed = 1;
}
if (!spec->playing)
{
if (spec->headset_play_format_setup_needed == 0)
{
cs_8409_headplay_cleanup(codec);
spec->headset_play_format_setup_needed = 1;
}
}
}
else
cs_8409_capture_cleanup(codec);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook done capture down");
spec->block_unsol = 0;
if (!list_empty(&spec->unsol_list))
{
codec_info(codec, "cs_8409_capture_pcm_hook - performing UNSOL responses\n");
cs_8409_perform_external_device_unsolicited_responses(codec);
}
// not sure of this position yet
spec->capturing = 0;
power_chk = snd_hda_codec_read(codec, codec->core.afg, 0, AC_VERB_GET_POWER_STATE, 0);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook power check 0x01 4 %d", power_chk);
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook HOOK CLEANUP end");
} else if (action == HDA_GEN_PCM_ACT_CLOSE) {
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook close");
myprintk("snd_hda_intel: command cs_8409_capture_pcm_hook close end");
}
}
// this version runs all explicit commands as logged on OSX
static int cs_8409_data_config(struct hda_codec *codec)
{
//struct cs8409_apple_spec *spec = codec->spec;
//hda_nid_t beep_nid = spec->beep_nid;
unsigned int tmpstate1 = -1;
unsigned int tmpstate2 = -1;
unsigned int tmpstate3 = -1;
unsigned int tmpstate4 = -1;
myprintk("snd_hda_intel: cs8409_data_config");
cs_8409_boot_setup_data(codec);
// check what power state of these nodes is - Apple does not do this
tmpstate1 = hda_sync_power_state_8409(codec, 0x48, AC_PWRST_D0);
tmpstate2 = hda_sync_power_state_8409(codec, 0x49, AC_PWRST_D0);
tmpstate3 = hda_sync_power_state_8409(codec, 0x4a, AC_PWRST_D0);
tmpstate4 = hda_sync_power_state_8409(codec, 0x4b, AC_PWRST_D0);
myprintk("snd_hda_intel: cs8409_data_config power 0x48 %d 0x49 %d 0x4a %d 0x4b %d\n",tmpstate1,tmpstate2,tmpstate3,tmpstate4);
myprintk("snd_hda_intel: cs8409_data_config end");
return 0;
}
// this version runs the setup using functions based on the setup using the logged data
static int cs_8409_real_config(struct hda_codec *codec)
{
//struct cs8409_apple_spec *spec = codec->spec;
//hda_nid_t beep_nid = spec->beep_nid;
unsigned int tmpstate1 = -1;
unsigned int tmpstate2 = -1;
unsigned int tmpstate3 = -1;
unsigned int tmpstate4 = -1;
myprintk("snd_hda_intel: cs8409_real_config");
cs_8409_boot_setup_real(codec);
// check what power state of these nodes is - Apple does not do this
tmpstate1 = hda_sync_power_state_8409(codec, 0x48, AC_PWRST_D0);
tmpstate2 = hda_sync_power_state_8409(codec, 0x49, AC_PWRST_D0);
tmpstate3 = hda_sync_power_state_8409(codec, 0x4a, AC_PWRST_D0);
tmpstate4 = hda_sync_power_state_8409(codec, 0x4b, AC_PWRST_D0);
myprintk("snd_hda_intel: cs8409_real_config power 0x48 %d 0x49 %d 0x4a %d 0x4b %d\n",tmpstate1,tmpstate2,tmpstate3,tmpstate4);
myprintk("snd_hda_intel: cs8409_real_config end");
return 0;
}