diff options
Diffstat (limited to 'sound/i2c/i2c.c')
-rw-r--r-- | sound/i2c/i2c.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/sound/i2c/i2c.c b/sound/i2c/i2c.c new file mode 100644 index 0000000000..847e3b6ca6 --- /dev/null +++ b/sound/i2c/i2c.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic i2c interface for ALSA + * + * (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de> + * Modified for the ALSA driver by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <sound/core.h> +#include <sound/i2c.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Generic i2c interface for ALSA"); +MODULE_LICENSE("GPL"); + +static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count); +static int snd_i2c_bit_readbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count); +static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus, + unsigned short addr); + +static const struct snd_i2c_ops snd_i2c_bit_ops = { + .sendbytes = snd_i2c_bit_sendbytes, + .readbytes = snd_i2c_bit_readbytes, + .probeaddr = snd_i2c_bit_probeaddr, +}; + +static int snd_i2c_bus_free(struct snd_i2c_bus *bus) +{ + struct snd_i2c_bus *slave; + struct snd_i2c_device *device; + + if (snd_BUG_ON(!bus)) + return -EINVAL; + while (!list_empty(&bus->devices)) { + device = snd_i2c_device(bus->devices.next); + snd_i2c_device_free(device); + } + if (bus->master) + list_del(&bus->buses); + else { + while (!list_empty(&bus->buses)) { + slave = snd_i2c_slave_bus(bus->buses.next); + snd_device_free(bus->card, slave); + } + } + if (bus->private_free) + bus->private_free(bus); + kfree(bus); + return 0; +} + +static int snd_i2c_bus_dev_free(struct snd_device *device) +{ + struct snd_i2c_bus *bus = device->device_data; + return snd_i2c_bus_free(bus); +} + +int snd_i2c_bus_create(struct snd_card *card, const char *name, + struct snd_i2c_bus *master, struct snd_i2c_bus **ri2c) +{ + struct snd_i2c_bus *bus; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_i2c_bus_dev_free, + }; + + *ri2c = NULL; + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (bus == NULL) + return -ENOMEM; + mutex_init(&bus->lock_mutex); + INIT_LIST_HEAD(&bus->devices); + INIT_LIST_HEAD(&bus->buses); + bus->card = card; + bus->ops = &snd_i2c_bit_ops; + if (master) { + list_add_tail(&bus->buses, &master->buses); + bus->master = master; + } + strscpy(bus->name, name, sizeof(bus->name)); + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &ops); + if (err < 0) { + snd_i2c_bus_free(bus); + return err; + } + *ri2c = bus; + return 0; +} + +EXPORT_SYMBOL(snd_i2c_bus_create); + +int snd_i2c_device_create(struct snd_i2c_bus *bus, const char *name, + unsigned char addr, struct snd_i2c_device **rdevice) +{ + struct snd_i2c_device *device; + + *rdevice = NULL; + if (snd_BUG_ON(!bus)) + return -EINVAL; + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (device == NULL) + return -ENOMEM; + device->addr = addr; + strscpy(device->name, name, sizeof(device->name)); + list_add_tail(&device->list, &bus->devices); + device->bus = bus; + *rdevice = device; + return 0; +} + +EXPORT_SYMBOL(snd_i2c_device_create); + +int snd_i2c_device_free(struct snd_i2c_device *device) +{ + if (device->bus) + list_del(&device->list); + if (device->private_free) + device->private_free(device); + kfree(device); + return 0; +} + +EXPORT_SYMBOL(snd_i2c_device_free); + +int snd_i2c_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + return device->bus->ops->sendbytes(device, bytes, count); +} + +EXPORT_SYMBOL(snd_i2c_sendbytes); + +int snd_i2c_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count) +{ + return device->bus->ops->readbytes(device, bytes, count); +} + +EXPORT_SYMBOL(snd_i2c_readbytes); + +int snd_i2c_probeaddr(struct snd_i2c_bus *bus, unsigned short addr) +{ + return bus->ops->probeaddr(bus, addr); +} + +EXPORT_SYMBOL(snd_i2c_probeaddr); + +/* + * bit-operations + */ + +static inline void snd_i2c_bit_hw_start(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->start) + bus->hw_ops.bit->start(bus); +} + +static inline void snd_i2c_bit_hw_stop(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->stop) + bus->hw_ops.bit->stop(bus); +} + +static void snd_i2c_bit_direction(struct snd_i2c_bus *bus, int clock, int data) +{ + if (bus->hw_ops.bit->direction) + bus->hw_ops.bit->direction(bus, clock, data); +} + +static void snd_i2c_bit_set(struct snd_i2c_bus *bus, int clock, int data) +{ + bus->hw_ops.bit->setlines(bus, clock, data); +} + +#if 0 +static int snd_i2c_bit_clock(struct snd_i2c_bus *bus) +{ + if (bus->hw_ops.bit->getclock) + return bus->hw_ops.bit->getclock(bus); + return -ENXIO; +} +#endif + +static int snd_i2c_bit_data(struct snd_i2c_bus *bus, int ack) +{ + return bus->hw_ops.bit->getdata(bus, ack); +} + +static void snd_i2c_bit_start(struct snd_i2c_bus *bus) +{ + snd_i2c_bit_hw_start(bus); + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_set(bus, 1, 0); + snd_i2c_bit_set(bus, 0, 0); +} + +static void snd_i2c_bit_stop(struct snd_i2c_bus *bus) +{ + snd_i2c_bit_set(bus, 0, 0); + snd_i2c_bit_set(bus, 1, 0); + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_hw_stop(bus); +} + +static void snd_i2c_bit_send(struct snd_i2c_bus *bus, int data) +{ + snd_i2c_bit_set(bus, 0, data); + snd_i2c_bit_set(bus, 1, data); + snd_i2c_bit_set(bus, 0, data); +} + +static int snd_i2c_bit_ack(struct snd_i2c_bus *bus) +{ + int ack; + + snd_i2c_bit_set(bus, 0, 1); + snd_i2c_bit_set(bus, 1, 1); + snd_i2c_bit_direction(bus, 1, 0); /* SCL - wr, SDA - rd */ + ack = snd_i2c_bit_data(bus, 1); + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_set(bus, 0, 1); + return ack ? -EIO : 0; +} + +static int snd_i2c_bit_sendbyte(struct snd_i2c_bus *bus, unsigned char data) +{ + int i, err; + + for (i = 7; i >= 0; i--) + snd_i2c_bit_send(bus, !!(data & (1 << i))); + err = snd_i2c_bit_ack(bus); + if (err < 0) + return err; + return 0; +} + +static int snd_i2c_bit_readbyte(struct snd_i2c_bus *bus, int last) +{ + int i; + unsigned char data = 0; + + snd_i2c_bit_set(bus, 0, 1); + snd_i2c_bit_direction(bus, 1, 0); /* SCL - wr, SDA - rd */ + for (i = 7; i >= 0; i--) { + snd_i2c_bit_set(bus, 1, 1); + if (snd_i2c_bit_data(bus, 0)) + data |= (1 << i); + snd_i2c_bit_set(bus, 0, 1); + } + snd_i2c_bit_direction(bus, 1, 1); /* SCL - wr, SDA - wr */ + snd_i2c_bit_send(bus, !!last); + return data; +} + +static int snd_i2c_bit_sendbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count) +{ + struct snd_i2c_bus *bus = device->bus; + int err, res = 0; + + if (device->flags & SND_I2C_DEVICE_ADDRTEN) + return -EIO; /* not yet implemented */ + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, device->addr << 1); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + while (count-- > 0) { + err = snd_i2c_bit_sendbyte(bus, *bytes++); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + res++; + } + snd_i2c_bit_stop(bus); + return res; +} + +static int snd_i2c_bit_readbytes(struct snd_i2c_device *device, + unsigned char *bytes, int count) +{ + struct snd_i2c_bus *bus = device->bus; + int err, res = 0; + + if (device->flags & SND_I2C_DEVICE_ADDRTEN) + return -EIO; /* not yet implemented */ + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, (device->addr << 1) | 1); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + while (count-- > 0) { + err = snd_i2c_bit_readbyte(bus, count == 0); + if (err < 0) { + snd_i2c_bit_hw_stop(bus); + return err; + } + *bytes++ = (unsigned char)err; + res++; + } + snd_i2c_bit_stop(bus); + return res; +} + +static int snd_i2c_bit_probeaddr(struct snd_i2c_bus *bus, unsigned short addr) +{ + int err; + + if (addr & 0x8000) /* 10-bit address */ + return -EIO; /* not yet implemented */ + if (addr & 0x7f80) /* invalid address */ + return -EINVAL; + snd_i2c_bit_start(bus); + err = snd_i2c_bit_sendbyte(bus, addr << 1); + snd_i2c_bit_stop(bus); + return err; +} |