18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * C-Media CMI8787 driver for the Studio Evolution SE6X
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/*
98c2ecf20Sopenharmony_ci * CMI8787:
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci *   SPI    -> microcontroller (not actually used)
128c2ecf20Sopenharmony_ci *   GPIO 0 -> do.
138c2ecf20Sopenharmony_ci *   GPIO 2 -> do.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci *   DAC0   -> both PCM1792A (L+R, each in mono mode)
168c2ecf20Sopenharmony_ci *   ADC1  <-  1st PCM1804
178c2ecf20Sopenharmony_ci *   ADC2  <-  2nd PCM1804
188c2ecf20Sopenharmony_ci *   ADC3  <-  3rd PCM1804
198c2ecf20Sopenharmony_ci */
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <linux/pci.h>
228c2ecf20Sopenharmony_ci#include <linux/module.h>
238c2ecf20Sopenharmony_ci#include <sound/core.h>
248c2ecf20Sopenharmony_ci#include <sound/control.h>
258c2ecf20Sopenharmony_ci#include <sound/initval.h>
268c2ecf20Sopenharmony_ci#include <sound/pcm.h>
278c2ecf20Sopenharmony_ci#include "oxygen.h"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ciMODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
308c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Studio Evolution SE6X driver");
318c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
328c2ecf20Sopenharmony_ciMODULE_SUPPORTED_DEVICE("{{Studio Evolution,SE6X}}");
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
358c2ecf20Sopenharmony_cistatic char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
368c2ecf20Sopenharmony_cistatic bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cimodule_param_array(index, int, NULL, 0444);
398c2ecf20Sopenharmony_ciMODULE_PARM_DESC(index, "card index");
408c2ecf20Sopenharmony_cimodule_param_array(id, charp, NULL, 0444);
418c2ecf20Sopenharmony_ciMODULE_PARM_DESC(id, "ID string");
428c2ecf20Sopenharmony_cimodule_param_array(enable, bool, NULL, 0444);
438c2ecf20Sopenharmony_ciMODULE_PARM_DESC(enable, "enable card");
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic const struct pci_device_id se6x_ids[] = {
468c2ecf20Sopenharmony_ci	{ OXYGEN_PCI_SUBID(0x13f6, 0x8788) },
478c2ecf20Sopenharmony_ci	{ }
488c2ecf20Sopenharmony_ci};
498c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, se6x_ids);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic void se6x_init(struct oxygen *chip)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x005);
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	snd_component_add(chip->card, "PCM1792A");
568c2ecf20Sopenharmony_ci	snd_component_add(chip->card, "PCM1804");
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic int se6x_control_filter(struct snd_kcontrol_new *template)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	/* no DAC volume/mute */
628c2ecf20Sopenharmony_ci	if (!strncmp(template->name, "Master Playback ", 16))
638c2ecf20Sopenharmony_ci		return 1;
648c2ecf20Sopenharmony_ci	return 0;
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic void se6x_cleanup(struct oxygen *chip)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistatic void set_pcm1792a_params(struct oxygen *chip,
728c2ecf20Sopenharmony_ci				struct snd_pcm_hw_params *params)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	/* nothing to do (the microcontroller monitors DAC_LRCK) */
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic void set_pcm1804_params(struct oxygen *chip,
788c2ecf20Sopenharmony_ci			       struct snd_pcm_hw_params *params)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic unsigned int se6x_adjust_dac_routing(struct oxygen *chip,
838c2ecf20Sopenharmony_ci					    unsigned int play_routing)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	/* route the same stereo pair to DAC0 and DAC1 */
868c2ecf20Sopenharmony_ci	return ( play_routing       & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
878c2ecf20Sopenharmony_ci	       ((play_routing << 2) & OXYGEN_PLAY_DAC1_SOURCE_MASK);
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic const struct oxygen_model model_se6x = {
918c2ecf20Sopenharmony_ci	.shortname = "Studio Evolution SE6X",
928c2ecf20Sopenharmony_ci	.longname = "C-Media Oxygen HD Audio",
938c2ecf20Sopenharmony_ci	.chip = "CMI8787",
948c2ecf20Sopenharmony_ci	.init = se6x_init,
958c2ecf20Sopenharmony_ci	.control_filter = se6x_control_filter,
968c2ecf20Sopenharmony_ci	.cleanup = se6x_cleanup,
978c2ecf20Sopenharmony_ci	.set_dac_params = set_pcm1792a_params,
988c2ecf20Sopenharmony_ci	.set_adc_params = set_pcm1804_params,
998c2ecf20Sopenharmony_ci	.adjust_dac_routing = se6x_adjust_dac_routing,
1008c2ecf20Sopenharmony_ci	.device_config = PLAYBACK_0_TO_I2S |
1018c2ecf20Sopenharmony_ci			 CAPTURE_0_FROM_I2S_1 |
1028c2ecf20Sopenharmony_ci			 CAPTURE_2_FROM_I2S_2 |
1038c2ecf20Sopenharmony_ci			 CAPTURE_3_FROM_I2S_3,
1048c2ecf20Sopenharmony_ci	.dac_channels_pcm = 2,
1058c2ecf20Sopenharmony_ci	.function_flags = OXYGEN_FUNCTION_SPI,
1068c2ecf20Sopenharmony_ci	.dac_mclks = OXYGEN_MCLKS(256, 128, 128),
1078c2ecf20Sopenharmony_ci	.adc_mclks = OXYGEN_MCLKS(256, 256, 128),
1088c2ecf20Sopenharmony_ci	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
1098c2ecf20Sopenharmony_ci	.adc_i2s_format = OXYGEN_I2S_FORMAT_I2S,
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int se6x_get_model(struct oxygen *chip,
1138c2ecf20Sopenharmony_ci			  const struct pci_device_id *pci_id)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	chip->model = model_se6x;
1168c2ecf20Sopenharmony_ci	return 0;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int se6x_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	static int dev;
1228c2ecf20Sopenharmony_ci	int err;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (dev >= SNDRV_CARDS)
1258c2ecf20Sopenharmony_ci		return -ENODEV;
1268c2ecf20Sopenharmony_ci	if (!enable[dev]) {
1278c2ecf20Sopenharmony_ci		++dev;
1288c2ecf20Sopenharmony_ci		return -ENOENT;
1298c2ecf20Sopenharmony_ci	}
1308c2ecf20Sopenharmony_ci	err = oxygen_pci_probe(pci, index[dev], id[dev], THIS_MODULE,
1318c2ecf20Sopenharmony_ci			       se6x_ids, se6x_get_model);
1328c2ecf20Sopenharmony_ci	if (err >= 0)
1338c2ecf20Sopenharmony_ci		++dev;
1348c2ecf20Sopenharmony_ci	return err;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic struct pci_driver se6x_driver = {
1388c2ecf20Sopenharmony_ci	.name = KBUILD_MODNAME,
1398c2ecf20Sopenharmony_ci	.id_table = se6x_ids,
1408c2ecf20Sopenharmony_ci	.probe = se6x_probe,
1418c2ecf20Sopenharmony_ci	.remove = oxygen_pci_remove,
1428c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
1438c2ecf20Sopenharmony_ci	.driver = {
1448c2ecf20Sopenharmony_ci		.pm = &oxygen_pci_pm,
1458c2ecf20Sopenharmony_ci	},
1468c2ecf20Sopenharmony_ci#endif
1478c2ecf20Sopenharmony_ci	.shutdown = oxygen_pci_shutdown,
1488c2ecf20Sopenharmony_ci};
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cimodule_pci_driver(se6x_driver);
151