#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/resource.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <machine/clock.h>
#include <dev/video/cxm/cxm.h>
#include <bus/iicbus/iiconf.h>
#include <bus/iicbus/iicbus.h>
#include "iicbb_if.h"
static const struct cxm_msp_command
msp34x5G_init = {
5,
{
{ CXM_MSP3400C_DEM, 0x0030, { 0x20, 0x03 } },
{ CXM_MSP3400C_DFP, 0x000d, { 0x19, 0x00 } },
{ CXM_MSP3400C_DFP, 0x000e, { 0x24, 0x03 } },
{ CXM_MSP3400C_DFP, 0x0010, { 0x5a, 0x00 } },
{ CXM_MSP3400C_DEM, 0x0020, { 0x00, 0x01 } },
}
};
static const struct cxm_msp_command
msp34x5G_select_tuner = {
3,
{
{ CXM_MSP3400C_DFP, 0x0008, { 0x03, 0x20 } },
{ CXM_MSP3400C_DFP, 0x000a, { 0x03, 0x20 } },
{ CXM_MSP3400C_DFP, 0x0013, { 0x0f, 0x20 } }
}
};
static const struct cxm_msp_command
msp34x5D_init = {
4,
{
{ CXM_MSP3400C_DEM, 0x0021, { 0x00, 0x01 } },
{ CXM_MSP3400C_DFP, 0x000d, { 0x19, 0x00 } },
{ CXM_MSP3400C_DFP, 0x0010, { 0x5a, 0x00 } },
{ CXM_MSP3400C_DEM, 0x0020, { 0x00, 0x01 } },
}
};
static const struct cxm_msp_command
msp34x5D_select_tuner = {
5,
{
{ CXM_MSP3400C_DFP, 0x0008, { 0x01, 0x20 } },
{ CXM_MSP3400C_DFP, 0x000a, { 0x01, 0x20 } },
{ CXM_MSP3400C_DFP, 0x000e, { 0x24, 0x00 } },
{ CXM_MSP3400C_DFP, 0x000f, { 0x00, 0x00 } },
{ CXM_MSP3400C_DFP, 0x0013, { 0x0f, 0x20 } }
}
};
static const struct cxm_msp_command
msp34xxx_mute = {
2,
{
{ CXM_MSP3400C_DFP, 0x0000, { 0x00, 0x00 } },
{ CXM_MSP3400C_DFP, 0x0007, { 0x00, 0x01 } }
}
};
static const struct cxm_msp_command
msp34xxx_unmute = {
2,
{
{ CXM_MSP3400C_DFP, 0x0000, { 0x73, 0x00 } },
{ CXM_MSP3400C_DFP, 0x0007, { 0x73, 0x01 } }
}
};
static const struct cxm_msp_command
msp34xxx_select_fm = {
3,
{
{ CXM_MSP3400C_DFP, 0x0008, { 0x02, 0x20 } },
{ CXM_MSP3400C_DFP, 0x000a, { 0x02, 0x20 } },
{ CXM_MSP3400C_DFP, 0x0013, { 0x0e, 0x00 } }
}
};
static const struct cxm_msp_command
msp34xxx_select_line_in = {
3,
{
{ CXM_MSP3400C_DFP, 0x0008, { 0x02, 0x20 } },
{ CXM_MSP3400C_DFP, 0x000a, { 0x02, 0x20 } },
{ CXM_MSP3400C_DFP, 0x0013, { 0x0c, 0x00 } }
}
};
static int
cxm_msp_dpl_reset(device_t iicbus, int i2c_addr)
{
unsigned char msg[3];
int sent;
msg[0] = 0x00;
msg[1] = 0x80;
msg[2] = 0x00;
if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
return -1;
if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
|| sent != sizeof(msg))
goto fail;
iicbus_stop(iicbus);
msg[0] = 0x00;
msg[1] = 0x00;
msg[2] = 0x00;
if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
return -1;
if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
|| sent != sizeof(msg))
goto fail;
iicbus_stop(iicbus);
return 0;
fail:
iicbus_stop(iicbus);
return -1;
}
static int
cxm_msp_dpl_read(device_t iicbus, int i2c_addr,
unsigned char dev, unsigned int addr,
char *buf, int len)
{
unsigned char msg[3];
int received;
int sent;
msg[0] = (unsigned char)(dev + 1);
msg[1] = (unsigned char)(addr >> 8);
msg[2] = (unsigned char)addr;
if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
return -1;
if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
|| sent != sizeof(msg))
goto fail;
if (iicbus_repeated_start(iicbus, i2c_addr + 1, CXM_I2C_TIMEOUT) != 0)
goto fail;
if (iicbus_read(iicbus, buf, len, &received, IIC_LAST_READ, 0) != 0)
goto fail;
iicbus_stop(iicbus);
return received;
fail:
iicbus_stop(iicbus);
return -1;
}
static int
cxm_msp_dpl_write(device_t iicbus, int i2c_addr,
unsigned char dev, unsigned int addr,
const char *buf, int len)
{
unsigned char msg[3];
int sent;
msg[0] = dev;
msg[1] = (unsigned char)(addr >> 8);
msg[2] = (unsigned char)addr;
if (iicbus_start(iicbus, i2c_addr, CXM_I2C_TIMEOUT) != 0)
return -1;
if (iicbus_write(iicbus, msg, sizeof(msg), &sent, CXM_I2C_TIMEOUT) != 0
|| sent != sizeof(msg))
goto fail;
if (iicbus_write(iicbus, buf, len, &sent, CXM_I2C_TIMEOUT) != 0)
goto fail;
iicbus_stop(iicbus);
return sent;
fail:
iicbus_stop(iicbus);
return -1;
}
int
cxm_msp_init(struct cxm_softc *sc)
{
unsigned char rev1[2];
unsigned char rev2[2];
unsigned int i;
unsigned int nsettings;
const struct cxm_msp_setting *settings;
if (cxm_msp_dpl_reset (sc->iicbus, CXM_I2C_MSP3400) < 0)
return -1;
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x001e, rev1, sizeof(rev1)) != sizeof(rev1))
return -1;
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x001f, rev2, sizeof(rev2)) != sizeof(rev2))
return -1;
ksnprintf(sc->msp_name, sizeof(sc->msp_name), "%c4%02d%c-%c%d",
((rev1[1] >> 4) & 0x0f) + '3', rev2[0],
(rev1[1] & 0x0f) + '@', rev1[0] + '@', rev2[1] & 0x1f);
if (strncmp(&sc->msp_name[0], "34", 2) == 0
&& strncmp(&sc->msp_name[3], "5D", 2) == 0)
;
else if (strncmp(&sc->msp_name[0], "34", 2) == 0
&& strncmp(&sc->msp_name[3], "5G", 2) == 0)
;
else if (strncmp(&sc->msp_name[0], "44", 2) == 0
&& strncmp(&sc->msp_name[3], "8G", 2) == 0)
;
else {
device_printf(sc->dev, "unknown audio decoder MSP%s\n",
sc->msp_name);
return -1;
}
nsettings = msp34x5G_init.nsettings;
settings = msp34x5G_init.settings;
if (sc->msp_name[4] == 'D') {
nsettings = msp34x5D_init.nsettings;
settings = msp34x5D_init.settings;
}
for (i = 0; i < nsettings; i++)
if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
settings[i].dev, settings[i].addr,
settings[i].value,
sizeof(settings[i].value))
!= sizeof(settings[i].value))
return -1;
if (cxm_msp_select_source(sc, cxm_tuner_source) < 0)
return -1;
device_printf(sc->dev, "MSP%s audio decoder\n", sc->msp_name);
return 0;
}
int
cxm_msp_mute(struct cxm_softc *sc)
{
unsigned int i;
unsigned int nsettings;
const struct cxm_msp_setting *settings;
nsettings = msp34xxx_mute.nsettings;
settings = msp34xxx_mute.settings;
for (i = 0; i < nsettings; i++)
if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
settings[i].dev, settings[i].addr,
settings[i].value,
sizeof(settings[i].value))
!= sizeof(settings[i].value))
return -1;
return 0;
}
int
cxm_msp_unmute(struct cxm_softc *sc)
{
unsigned int i;
unsigned int nsettings;
const struct cxm_msp_setting *settings;
nsettings = msp34xxx_unmute.nsettings;
settings = msp34xxx_unmute.settings;
for (i = 0; i < nsettings; i++)
if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
settings[i].dev, settings[i].addr,
settings[i].value,
sizeof(settings[i].value))
!= sizeof(settings[i].value))
return -1;
return 0;
}
int
cxm_msp_is_muted(struct cxm_softc *sc)
{
unsigned char volume[2];
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x0000, volume, sizeof(volume)) != sizeof(volume))
return -1;
return volume[0] == 0x00 || volume[0] == 0xff ? 1 : 0;
}
int
cxm_msp_select_source(struct cxm_softc *sc, enum cxm_source source)
{
unsigned int i;
unsigned int nsettings;
const struct cxm_msp_setting *settings;
switch (source) {
case cxm_fm_source:
nsettings = msp34xxx_select_fm.nsettings;
settings = msp34xxx_select_fm.settings;
break;
case cxm_line_in_source_composite:
case cxm_line_in_source_svideo:
nsettings = msp34xxx_select_line_in.nsettings;
settings = msp34xxx_select_line_in.settings;
break;
case cxm_tuner_source:
nsettings = msp34x5G_select_tuner.nsettings;
settings = msp34x5G_select_tuner.settings;
if (sc->msp_name[4] == 'D') {
nsettings = msp34x5D_select_tuner.nsettings;
settings = msp34x5D_select_tuner.settings;
}
break;
default:
return -1;
}
for (i = 0; i < nsettings; i++)
if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
settings[i].dev, settings[i].addr,
settings[i].value,
sizeof(settings[i].value))
!= sizeof(settings[i].value))
return -1;
return 0;
}
enum cxm_source
cxm_msp_selected_source(struct cxm_softc *sc)
{
unsigned char dsp[2];
unsigned char source[2];
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x0008, source, sizeof(source)) != sizeof(source))
return cxm_unknown_source;
switch (source[0]) {
case 0:
case 1:
case 3:
case 4:
return cxm_tuner_source;
case 2:
break;
default:
return cxm_unknown_source;
}
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x0013, dsp, sizeof(dsp)) != sizeof(dsp))
return cxm_unknown_source;
if (dsp[1] & 0x20)
return cxm_unknown_source;
switch (dsp[0] & 0x03) {
case 0:
return cxm_line_in_source_composite;
case 2:
return cxm_fm_source;
default:
return cxm_unknown_source;
}
}
int
cxm_msp_autodetect_standard(struct cxm_softc *sc)
{
unsigned int i;
int locked;
unsigned int nsettings;
const struct cxm_msp_setting *settings;
switch (cxm_msp_selected_source(sc)) {
case cxm_tuner_source:
break;
case cxm_fm_source:
case cxm_line_in_source_composite:
case cxm_line_in_source_svideo:
return 1;
default:
return -1;
}
if (cxm_msp_dpl_reset (sc->iicbus, CXM_I2C_MSP3400) < 0)
return -1;
nsettings = msp34x5G_init.nsettings;
settings = msp34x5G_init.settings;
if (sc->msp_name[4] == 'D') {
nsettings = msp34x5D_init.nsettings;
settings = msp34x5D_init.settings;
}
for (i = 0; i < nsettings; i++)
if (cxm_msp_dpl_write(sc->iicbus, CXM_I2C_MSP3400,
settings[i].dev, settings[i].addr,
settings[i].value,
sizeof(settings[i].value))
!= sizeof(settings[i].value))
return -1;
locked = cxm_msp_wait_for_lock(sc);
if (cxm_msp_select_source(sc, cxm_tuner_source) < 0)
return -1;
return locked;
}
int
cxm_msp_is_locked(struct cxm_softc *sc)
{
unsigned char source[2];
unsigned char standard[2];
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DFP,
0x0008, source, sizeof(source)) != sizeof(source))
return -1;
switch (source[0]) {
case 0:
case 1:
case 3:
case 4:
break;
default:
return 1;
}
if (cxm_msp_dpl_read(sc->iicbus, CXM_I2C_MSP3400, CXM_MSP3400C_DEM,
0x007e, standard, sizeof(standard))
!= sizeof(standard))
return -1;
if (standard[0] >= 8 || (standard[0] == 0 && standard[1] == 0))
return 0;
return 1;
}
int
cxm_msp_wait_for_lock(struct cxm_softc *sc)
{
unsigned int i;
for (i = 0; i < 10; i++) {
tsleep(&sc->iicbus, 0, "audio", hz / 20);
switch (cxm_msp_is_locked(sc)) {
case 1:
return 1;
case 0:
break;
default:
return -1;
}
}
device_printf(sc->dev, "audio decoder failed to lock\n");
return 0;
}