1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * C-Media CMI8787 driver for the Studio Evolution SE6X |
4 | * |
5 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> |
6 | */ |
7 | |
8 | /* |
9 | * CMI8787: |
10 | * |
11 | * SPI -> microcontroller (not actually used) |
12 | * GPIO 0 -> do. |
13 | * GPIO 2 -> do. |
14 | * |
15 | * DAC0 -> both PCM1792A (L+R, each in mono mode) |
16 | * ADC1 <- 1st PCM1804 |
17 | * ADC2 <- 2nd PCM1804 |
18 | * ADC3 <- 3rd PCM1804 |
19 | */ |
20 | |
21 | #include <linux/pci.h> |
22 | #include <linux/module.h> |
23 | #include <sound/core.h> |
24 | #include <sound/control.h> |
25 | #include <sound/initval.h> |
26 | #include <sound/pcm.h> |
27 | #include "oxygen.h" |
28 | |
29 | MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>" ); |
30 | MODULE_DESCRIPTION("Studio Evolution SE6X driver" ); |
31 | MODULE_LICENSE("GPL v2" ); |
32 | |
33 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
34 | static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; |
35 | static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; |
36 | |
37 | module_param_array(index, int, NULL, 0444); |
38 | MODULE_PARM_DESC(index, "card index" ); |
39 | module_param_array(id, charp, NULL, 0444); |
40 | MODULE_PARM_DESC(id, "ID string" ); |
41 | module_param_array(enable, bool, NULL, 0444); |
42 | MODULE_PARM_DESC(enable, "enable card" ); |
43 | |
44 | static const struct pci_device_id se6x_ids[] = { |
45 | { OXYGEN_PCI_SUBID(0x13f6, 0x8788) }, |
46 | { } |
47 | }; |
48 | MODULE_DEVICE_TABLE(pci, se6x_ids); |
49 | |
50 | static void se6x_init(struct oxygen *chip) |
51 | { |
52 | oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, value: 0x005); |
53 | |
54 | snd_component_add(card: chip->card, component: "PCM1792A" ); |
55 | snd_component_add(card: chip->card, component: "PCM1804" ); |
56 | } |
57 | |
58 | static int se6x_control_filter(struct snd_kcontrol_new *template) |
59 | { |
60 | /* no DAC volume/mute */ |
61 | if (!strncmp(template->name, "Master Playback " , 16)) |
62 | return 1; |
63 | return 0; |
64 | } |
65 | |
66 | static void se6x_cleanup(struct oxygen *chip) |
67 | { |
68 | } |
69 | |
70 | static void set_pcm1792a_params(struct oxygen *chip, |
71 | struct snd_pcm_hw_params *params) |
72 | { |
73 | /* nothing to do (the microcontroller monitors DAC_LRCK) */ |
74 | } |
75 | |
76 | static void set_pcm1804_params(struct oxygen *chip, |
77 | struct snd_pcm_hw_params *params) |
78 | { |
79 | } |
80 | |
81 | static unsigned int se6x_adjust_dac_routing(struct oxygen *chip, |
82 | unsigned int play_routing) |
83 | { |
84 | /* route the same stereo pair to DAC0 and DAC1 */ |
85 | return ( play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) | |
86 | ((play_routing << 2) & OXYGEN_PLAY_DAC1_SOURCE_MASK); |
87 | } |
88 | |
89 | static const struct oxygen_model model_se6x = { |
90 | .shortname = "Studio Evolution SE6X" , |
91 | .longname = "C-Media Oxygen HD Audio" , |
92 | .chip = "CMI8787" , |
93 | .init = se6x_init, |
94 | .control_filter = se6x_control_filter, |
95 | .cleanup = se6x_cleanup, |
96 | .set_dac_params = set_pcm1792a_params, |
97 | .set_adc_params = set_pcm1804_params, |
98 | .adjust_dac_routing = se6x_adjust_dac_routing, |
99 | .device_config = PLAYBACK_0_TO_I2S | |
100 | CAPTURE_0_FROM_I2S_1 | |
101 | CAPTURE_2_FROM_I2S_2 | |
102 | CAPTURE_3_FROM_I2S_3, |
103 | .dac_channels_pcm = 2, |
104 | .function_flags = OXYGEN_FUNCTION_SPI, |
105 | .dac_mclks = OXYGEN_MCLKS(256, 128, 128), |
106 | .adc_mclks = OXYGEN_MCLKS(256, 256, 128), |
107 | .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, |
108 | .adc_i2s_format = OXYGEN_I2S_FORMAT_I2S, |
109 | }; |
110 | |
111 | static int se6x_get_model(struct oxygen *chip, |
112 | const struct pci_device_id *pci_id) |
113 | { |
114 | chip->model = model_se6x; |
115 | return 0; |
116 | } |
117 | |
118 | static int se6x_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) |
119 | { |
120 | static int dev; |
121 | int err; |
122 | |
123 | if (dev >= SNDRV_CARDS) |
124 | return -ENODEV; |
125 | if (!enable[dev]) { |
126 | ++dev; |
127 | return -ENOENT; |
128 | } |
129 | err = oxygen_pci_probe(pci, index: index[dev], id: id[dev], THIS_MODULE, |
130 | ids: se6x_ids, get_model: se6x_get_model); |
131 | if (err >= 0) |
132 | ++dev; |
133 | return err; |
134 | } |
135 | |
136 | static struct pci_driver se6x_driver = { |
137 | .name = KBUILD_MODNAME, |
138 | .id_table = se6x_ids, |
139 | .probe = se6x_probe, |
140 | #ifdef CONFIG_PM_SLEEP |
141 | .driver = { |
142 | .pm = &oxygen_pci_pm, |
143 | }, |
144 | #endif |
145 | .shutdown = oxygen_pci_shutdown, |
146 | }; |
147 | |
148 | module_pci_driver(se6x_driver); |
149 | |