18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   ALSA Driver for the PT2258 volume controller.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *	Copyright (c) 2006  Jochen Voss <voss@seehuhn.de>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <sound/core.h>
98c2ecf20Sopenharmony_ci#include <sound/control.h>
108c2ecf20Sopenharmony_ci#include <sound/tlv.h>
118c2ecf20Sopenharmony_ci#include <sound/i2c.h>
128c2ecf20Sopenharmony_ci#include <sound/pt2258.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jochen Voss <voss@seehuhn.de>");
168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PT2258 volume controller (Princeton Technology Corp.)");
178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define PT2258_CMD_RESET 0xc0
208c2ecf20Sopenharmony_ci#define PT2258_CMD_UNMUTE 0xf8
218c2ecf20Sopenharmony_ci#define PT2258_CMD_MUTE 0xf9
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic const unsigned char pt2258_channel_code[12] = {
248c2ecf20Sopenharmony_ci	0x80, 0x90,		/* channel 1: -10dB, -1dB */
258c2ecf20Sopenharmony_ci	0x40, 0x50,		/* channel 2: -10dB, -1dB */
268c2ecf20Sopenharmony_ci	0x00, 0x10,		/* channel 3: -10dB, -1dB */
278c2ecf20Sopenharmony_ci	0x20, 0x30,		/* channel 4: -10dB, -1dB */
288c2ecf20Sopenharmony_ci	0x60, 0x70,		/* channel 5: -10dB, -1dB */
298c2ecf20Sopenharmony_ci	0xa0, 0xb0		/* channel 6: -10dB, -1dB */
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciint snd_pt2258_reset(struct snd_pt2258 *pt)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	unsigned char bytes[2];
358c2ecf20Sopenharmony_ci	int i;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	/* reset chip */
388c2ecf20Sopenharmony_ci	bytes[0] = PT2258_CMD_RESET;
398c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
408c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
418c2ecf20Sopenharmony_ci		goto __error;
428c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	/* mute all channels */
458c2ecf20Sopenharmony_ci	pt->mute = 1;
468c2ecf20Sopenharmony_ci	bytes[0] = PT2258_CMD_MUTE;
478c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
488c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
498c2ecf20Sopenharmony_ci		goto __error;
508c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	/* set all channels to 0dB */
538c2ecf20Sopenharmony_ci	for (i = 0; i < 6; ++i)
548c2ecf20Sopenharmony_ci		pt->volume[i] = 0;
558c2ecf20Sopenharmony_ci	bytes[0] = 0xd0;
568c2ecf20Sopenharmony_ci	bytes[1] = 0xe0;
578c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
588c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
598c2ecf20Sopenharmony_ci		goto __error;
608c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return 0;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci      __error:
658c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
668c2ecf20Sopenharmony_ci	snd_printk(KERN_ERR "PT2258 reset failed\n");
678c2ecf20Sopenharmony_ci	return -EIO;
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int pt2258_stereo_volume_info(struct snd_kcontrol *kcontrol,
718c2ecf20Sopenharmony_ci				     struct snd_ctl_elem_info *uinfo)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
748c2ecf20Sopenharmony_ci	uinfo->count = 2;
758c2ecf20Sopenharmony_ci	uinfo->value.integer.min = 0;
768c2ecf20Sopenharmony_ci	uinfo->value.integer.max = 79;
778c2ecf20Sopenharmony_ci	return 0;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int pt2258_stereo_volume_get(struct snd_kcontrol *kcontrol,
818c2ecf20Sopenharmony_ci				    struct snd_ctl_elem_value *ucontrol)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct snd_pt2258 *pt = kcontrol->private_data;
848c2ecf20Sopenharmony_ci	int base = kcontrol->private_value;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	/* chip does not support register reads */
878c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = 79 - pt->volume[base];
888c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[1] = 79 - pt->volume[base + 1];
898c2ecf20Sopenharmony_ci	return 0;
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic int pt2258_stereo_volume_put(struct snd_kcontrol *kcontrol,
938c2ecf20Sopenharmony_ci				    struct snd_ctl_elem_value *ucontrol)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct snd_pt2258 *pt = kcontrol->private_data;
968c2ecf20Sopenharmony_ci	int base = kcontrol->private_value;
978c2ecf20Sopenharmony_ci	unsigned char bytes[2];
988c2ecf20Sopenharmony_ci	int val0, val1;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	val0 = 79 - ucontrol->value.integer.value[0];
1018c2ecf20Sopenharmony_ci	val1 = 79 - ucontrol->value.integer.value[1];
1028c2ecf20Sopenharmony_ci	if (val0 < 0 || val0 > 79 || val1 < 0 || val1 > 79)
1038c2ecf20Sopenharmony_ci		return -EINVAL;
1048c2ecf20Sopenharmony_ci	if (val0 == pt->volume[base] && val1 == pt->volume[base + 1])
1058c2ecf20Sopenharmony_ci		return 0;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	pt->volume[base] = val0;
1088c2ecf20Sopenharmony_ci	bytes[0] = pt2258_channel_code[2 * base] | (val0 / 10);
1098c2ecf20Sopenharmony_ci	bytes[1] = pt2258_channel_code[2 * base + 1] | (val0 % 10);
1108c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
1118c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
1128c2ecf20Sopenharmony_ci		goto __error;
1138c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	pt->volume[base + 1] = val1;
1168c2ecf20Sopenharmony_ci	bytes[0] = pt2258_channel_code[2 * base + 2] | (val1 / 10);
1178c2ecf20Sopenharmony_ci	bytes[1] = pt2258_channel_code[2 * base + 3] | (val1 % 10);
1188c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
1198c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
1208c2ecf20Sopenharmony_ci		goto __error;
1218c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return 1;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci      __error:
1268c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
1278c2ecf20Sopenharmony_ci	snd_printk(KERN_ERR "PT2258 access failed\n");
1288c2ecf20Sopenharmony_ci	return -EIO;
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci#define pt2258_switch_info	snd_ctl_boolean_mono_info
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic int pt2258_switch_get(struct snd_kcontrol *kcontrol,
1348c2ecf20Sopenharmony_ci			     struct snd_ctl_elem_value *ucontrol)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	struct snd_pt2258 *pt = kcontrol->private_data;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = !pt->mute;
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic int pt2258_switch_put(struct snd_kcontrol *kcontrol,
1438c2ecf20Sopenharmony_ci			     struct snd_ctl_elem_value *ucontrol)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	struct snd_pt2258 *pt = kcontrol->private_data;
1468c2ecf20Sopenharmony_ci	unsigned char bytes[2];
1478c2ecf20Sopenharmony_ci	int val;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	val = !ucontrol->value.integer.value[0];
1508c2ecf20Sopenharmony_ci	if (pt->mute == val)
1518c2ecf20Sopenharmony_ci		return 0;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	pt->mute = val;
1548c2ecf20Sopenharmony_ci	bytes[0] = val ? PT2258_CMD_MUTE : PT2258_CMD_UNMUTE;
1558c2ecf20Sopenharmony_ci	snd_i2c_lock(pt->i2c_bus);
1568c2ecf20Sopenharmony_ci	if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
1578c2ecf20Sopenharmony_ci		goto __error;
1588c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	return 1;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci      __error:
1638c2ecf20Sopenharmony_ci	snd_i2c_unlock(pt->i2c_bus);
1648c2ecf20Sopenharmony_ci	snd_printk(KERN_ERR "PT2258 access failed 2\n");
1658c2ecf20Sopenharmony_ci	return -EIO;
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic const DECLARE_TLV_DB_SCALE(pt2258_db_scale, -7900, 100, 0);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ciint snd_pt2258_build_controls(struct snd_pt2258 *pt)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct snd_kcontrol_new knew;
1738c2ecf20Sopenharmony_ci	char *names[3] = {
1748c2ecf20Sopenharmony_ci		"Mic Loopback Playback Volume",
1758c2ecf20Sopenharmony_ci		"Line Loopback Playback Volume",
1768c2ecf20Sopenharmony_ci		"CD Loopback Playback Volume"
1778c2ecf20Sopenharmony_ci	};
1788c2ecf20Sopenharmony_ci	int i, err;
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	for (i = 0; i < 3; ++i) {
1818c2ecf20Sopenharmony_ci		memset(&knew, 0, sizeof(knew));
1828c2ecf20Sopenharmony_ci		knew.name = names[i];
1838c2ecf20Sopenharmony_ci		knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
1848c2ecf20Sopenharmony_ci		knew.count = 1;
1858c2ecf20Sopenharmony_ci		knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
1868c2ecf20Sopenharmony_ci		    SNDRV_CTL_ELEM_ACCESS_TLV_READ;
1878c2ecf20Sopenharmony_ci		knew.private_value = 2 * i;
1888c2ecf20Sopenharmony_ci		knew.info = pt2258_stereo_volume_info;
1898c2ecf20Sopenharmony_ci		knew.get = pt2258_stereo_volume_get;
1908c2ecf20Sopenharmony_ci		knew.put = pt2258_stereo_volume_put;
1918c2ecf20Sopenharmony_ci		knew.tlv.p = pt2258_db_scale;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
1948c2ecf20Sopenharmony_ci		if (err < 0)
1958c2ecf20Sopenharmony_ci			return err;
1968c2ecf20Sopenharmony_ci	}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	memset(&knew, 0, sizeof(knew));
1998c2ecf20Sopenharmony_ci	knew.name = "Loopback Switch";
2008c2ecf20Sopenharmony_ci	knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
2018c2ecf20Sopenharmony_ci	knew.info = pt2258_switch_info;
2028c2ecf20Sopenharmony_ci	knew.get = pt2258_switch_get;
2038c2ecf20Sopenharmony_ci	knew.put = pt2258_switch_put;
2048c2ecf20Sopenharmony_ci	knew.access = 0;
2058c2ecf20Sopenharmony_ci	err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
2068c2ecf20Sopenharmony_ci	if (err < 0)
2078c2ecf20Sopenharmony_ci		return err;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	return 0;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_pt2258_reset);
2138c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_pt2258_build_controls);
214