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