// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA driver for ICEnsemble ICE1712 (Envy24)
*
* Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire
*
* Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
* 2002 Takashi Iwai <tiwai@suse.de>
*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/cs8427.h>
#include <sound/asoundef.h>
#include "ice1712.h"
#include "ews.h"
#define SND_CS8404
#include <sound/cs8403.h>
enum {
EWS_I2C_CS8404 = 0, EWS_I2C_PCF1, EWS_I2C_PCF2,
EWS_I2C_88D = 0,
EWS_I2C_6FIRE = 0
};
/* additional i2c devices for EWS boards */
struct ews_spec {
struct snd_i2c_device *i2cdevs[3];
};
/*
* access via i2c mode (for EWX 24/96, EWS 88MT&D)
*/
/* send SDA and SCL */
static void ewx_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
{
struct snd_ice1712 *ice = bus->private_data;
unsigned char tmp = 0;
if (clk)
tmp |= ICE1712_EWX2496_SERIAL_CLOCK;
if (data)
tmp |= ICE1712_EWX2496_SERIAL_DATA;
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
udelay(5);
}
static int ewx_i2c_getclock(struct snd_i2c_bus *bus)
{
struct snd_ice1712 *ice = bus->private_data;
return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_CLOCK ? 1 : 0;
}
static int ewx_i2c_getdata(struct snd_i2c_bus *bus, int ack)
{
struct snd_ice1712 *ice = bus->private_data;
int bit;
/* set RW pin to low */
snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_RW);
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, 0);
if (ack)
udelay(5);
bit = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_DATA ? 1 : 0;
/* set RW pin to high */
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ICE1712_EWX2496_RW);
/* reset write mask */
snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_SERIAL_CLOCK);
return bit;
}
static void ewx_i2c_start(struct snd_i2c_bus *bus)
{
struct snd_ice1712 *ice = bus->private_data;
unsigned char mask;
snd_ice1712_save_gpio_status(ice);
/* set RW high */
mask = ICE1712_EWX2496_RW;
switch (ice->eeprom.subvendor) {
case ICE1712_SUBDEVICE_EWX2496:
mask |= ICE1712_EWX2496_AK4524_CS; /* CS high also */
break;
case ICE1712_SUBDEVICE_DMX6FIRE:
mask |= ICE1712_6FIRE_AK4524_CS_MASK; /* CS high also */
break;
}
snd_ice1712_gpio_write_bits(ice, mask, mask);
}
static void ewx_i2c_stop(struct snd_i2c_bus *bus)
{
struct snd_ice1712 *ice = bus->private_data;
snd_ice1712_restore_gpio_status(ice);
}
static void ewx_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
{
struct snd_ice1712 *ice = bus->private_data;
unsigned char mask = 0;
if (clock)
mask |= ICE1712_EWX2496_SERIAL_CLOCK; /* write SCL */
if (data)
mask |= ICE1712_EWX2496_SERIAL_DATA; /* write SDA */
ice->gpio.direction &= ~(ICE1712_EWX2496_SERIAL_CLOCK|ICE1712_EWX2496_SERIAL_DATA);
ice->gpio.direction |= mask;
snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->gpio.direction);
snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~mask);
}
static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = {
.start = ewx_i2c_start,
.stop = ewx_i2c_stop,
.direction = ewx_i2c_direction,
.setlines = ewx_i2c_setlines,
.getclock = ewx_i2c_getclock,
.getdata = ewx_i2c_getdata,
};
/*
* AK4524 access
*/
/* AK4524 chip select; address 0x48 bit 0-3 */
static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask)
{
struct ews_spec *spec = ice->spec;
unsigned char data, ndata;
if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
return -EINVAL;
snd_i2c_lock(ice->i2c);
if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1)
goto __error;
ndata = (data & 0xf0) | chip_mask;
if (ndata != data)
if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], &ndata, 1)
!= 1)
goto __error;
snd_i2c_unlock(ice->i2c);
return 0;
__error:
snd_i2c_unlock(ice->i2c);
dev_err(ice->card->dev,
"AK4524 chip select failed, check cable to the front module\n");
return -EIO;
}
/* start callback for EWS88MT, needs to select a certain chip mask */
static void ews88mt_ak4524_lock(struct snd_akm4xxx *ak, int chip)
{
struct snd_ice1712 *ice = ak->private_data[0];
unsigned char tmp;
/* assert AK4524 CS */
if (snd_ice1712_ews88mt_chip_select(ice, ~(1 <