diff options
author | Takashi Iwai <tiwai@suse.de> | 2015-03-02 17:22:59 -0500 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2015-03-23 08:17:02 -0400 |
commit | d068ebc25e6e1360510ad8023fe7bca3dacd204e (patch) | |
tree | 982ba3c4c1aa5f1a647f505d1d0863b926fde605 /sound/hda | |
parent | e3d280fc6d42017b2379503fbda83655a05294fe (diff) |
ALSA: hda - Move some codes up to hdac_bus struct
A few basic codes for communicating over HD-audio bus are moved to
struct hdac_bus now. It has only command and get_response ops in
addition to the unsolicited event handling.
Note that the codec-side tracing support is disabled temporarily
during this transition due to the code shuffling. It will be
re-enabled later once when all pieces are settled down.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/hda')
-rw-r--r-- | sound/hda/Makefile | 2 | ||||
-rw-r--r-- | sound/hda/hdac_bus.c | 181 |
2 files changed, 182 insertions, 1 deletions
diff --git a/sound/hda/Makefile b/sound/hda/Makefile index 59c8d1feb5aa..828680b282fa 100644 --- a/sound/hda/Makefile +++ b/sound/hda/Makefile | |||
@@ -1,3 +1,3 @@ | |||
1 | snd-hda-core-objs := hda_bus_type.o | 1 | snd-hda-core-objs := hda_bus_type.o hdac_bus.o |
2 | 2 | ||
3 | obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o | 3 | obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o |
diff --git a/sound/hda/hdac_bus.c b/sound/hda/hdac_bus.c new file mode 100644 index 000000000000..364f64c0e4a3 --- /dev/null +++ b/sound/hda/hdac_bus.c | |||
@@ -0,0 +1,181 @@ | |||
1 | /* | ||
2 | * HD-audio core bus driver | ||
3 | */ | ||
4 | |||
5 | #include <linux/init.h> | ||
6 | #include <linux/device.h> | ||
7 | #include <linux/module.h> | ||
8 | #include <linux/export.h> | ||
9 | #include <sound/hdaudio.h> | ||
10 | |||
11 | static void process_unsol_events(struct work_struct *work); | ||
12 | |||
13 | /** | ||
14 | * snd_hdac_bus_init - initialize a HD-audio bas bus | ||
15 | * @bus: the pointer to bus object | ||
16 | * | ||
17 | * Returns 0 if successful, or a negative error code. | ||
18 | */ | ||
19 | int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev, | ||
20 | const struct hdac_bus_ops *ops) | ||
21 | { | ||
22 | memset(bus, 0, sizeof(*bus)); | ||
23 | bus->dev = dev; | ||
24 | bus->ops = ops; | ||
25 | INIT_LIST_HEAD(&bus->codec_list); | ||
26 | INIT_WORK(&bus->unsol_work, process_unsol_events); | ||
27 | mutex_init(&bus->cmd_mutex); | ||
28 | return 0; | ||
29 | } | ||
30 | EXPORT_SYMBOL_GPL(snd_hdac_bus_init); | ||
31 | |||
32 | /** | ||
33 | * snd_hdac_bus_exit - clean up a HD-audio bas bus | ||
34 | * @bus: the pointer to bus object | ||
35 | */ | ||
36 | void snd_hdac_bus_exit(struct hdac_bus *bus) | ||
37 | { | ||
38 | WARN_ON(!list_empty(&bus->codec_list)); | ||
39 | cancel_work_sync(&bus->unsol_work); | ||
40 | } | ||
41 | EXPORT_SYMBOL_GPL(snd_hdac_bus_exit); | ||
42 | |||
43 | /** | ||
44 | * snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus | ||
45 | * @bus: bus object | ||
46 | * @cmd: HD-audio encoded verb | ||
47 | * @res: pointer to store the response, NULL if performing asynchronously | ||
48 | * | ||
49 | * Returns 0 if successful, or a negative error code. | ||
50 | */ | ||
51 | int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, | ||
52 | unsigned int cmd, unsigned int *res) | ||
53 | { | ||
54 | int err; | ||
55 | |||
56 | mutex_lock(&bus->cmd_mutex); | ||
57 | err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res); | ||
58 | mutex_unlock(&bus->cmd_mutex); | ||
59 | return err; | ||
60 | } | ||
61 | EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb); | ||
62 | |||
63 | /** | ||
64 | * snd_hdac_bus_exec_verb_unlocked - unlocked version | ||
65 | * @bus: bus object | ||
66 | * @cmd: HD-audio encoded verb | ||
67 | * @res: pointer to store the response, NULL if performing asynchronously | ||
68 | * | ||
69 | * Returns 0 if successful, or a negative error code. | ||
70 | */ | ||
71 | int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, | ||
72 | unsigned int cmd, unsigned int *res) | ||
73 | { | ||
74 | unsigned int tmp; | ||
75 | int err; | ||
76 | |||
77 | if (cmd == ~0) | ||
78 | return -EINVAL; | ||
79 | |||
80 | if (res) | ||
81 | *res = -1; | ||
82 | else if (bus->sync_write) | ||
83 | res = &tmp; | ||
84 | for (;;) { | ||
85 | err = bus->ops->command(bus, cmd); | ||
86 | if (err != -EAGAIN) | ||
87 | break; | ||
88 | /* process pending verbs */ | ||
89 | err = bus->ops->get_response(bus, addr, &tmp); | ||
90 | if (err) | ||
91 | break; | ||
92 | } | ||
93 | if (!err && res) | ||
94 | err = bus->ops->get_response(bus, addr, res); | ||
95 | return err; | ||
96 | } | ||
97 | EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked); | ||
98 | |||
99 | /** | ||
100 | * snd_hdac_bus_queue_event - add an unsolicited event to queue | ||
101 | * @bus: the BUS | ||
102 | * @res: unsolicited event (lower 32bit of RIRB entry) | ||
103 | * @res_ex: codec addr and flags (upper 32bit or RIRB entry) | ||
104 | * | ||
105 | * Adds the given event to the queue. The events are processed in | ||
106 | * the workqueue asynchronously. Call this function in the interrupt | ||
107 | * hanlder when RIRB receives an unsolicited event. | ||
108 | */ | ||
109 | void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex) | ||
110 | { | ||
111 | unsigned int wp; | ||
112 | |||
113 | if (!bus) | ||
114 | return; | ||
115 | |||
116 | wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE; | ||
117 | bus->unsol_wp = wp; | ||
118 | |||
119 | wp <<= 1; | ||
120 | bus->unsol_queue[wp] = res; | ||
121 | bus->unsol_queue[wp + 1] = res_ex; | ||
122 | |||
123 | schedule_work(&bus->unsol_work); | ||
124 | } | ||
125 | EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event); | ||
126 | |||
127 | /* | ||
128 | * process queued unsolicited events | ||
129 | */ | ||
130 | static void process_unsol_events(struct work_struct *work) | ||
131 | { | ||
132 | struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work); | ||
133 | struct hdac_device *codec; | ||
134 | struct hdac_driver *drv; | ||
135 | unsigned int rp, caddr, res; | ||
136 | |||
137 | while (bus->unsol_rp != bus->unsol_wp) { | ||
138 | rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE; | ||
139 | bus->unsol_rp = rp; | ||
140 | rp <<= 1; | ||
141 | res = bus->unsol_queue[rp]; | ||
142 | caddr = bus->unsol_queue[rp + 1]; | ||
143 | if (!(caddr & (1 << 4))) /* no unsolicited event? */ | ||
144 | continue; | ||
145 | codec = bus->caddr_tbl[caddr & 0x0f]; | ||
146 | if (!codec || !codec->dev.driver) | ||
147 | continue; | ||
148 | drv = drv_to_hdac_driver(codec->dev.driver); | ||
149 | if (drv->unsol_event) | ||
150 | drv->unsol_event(codec, res); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec) | ||
155 | { | ||
156 | if (bus->caddr_tbl[codec->addr]) { | ||
157 | dev_err(bus->dev, "address 0x%x is already occupied\n", | ||
158 | codec->addr); | ||
159 | return -EBUSY; | ||
160 | } | ||
161 | |||
162 | list_add_tail(&codec->list, &bus->codec_list); | ||
163 | bus->caddr_tbl[codec->addr] = codec; | ||
164 | set_bit(codec->addr, &bus->codec_powered); | ||
165 | bus->num_codecs++; | ||
166 | return 0; | ||
167 | } | ||
168 | EXPORT_SYMBOL_GPL(snd_hdac_bus_add_device); | ||
169 | |||
170 | void snd_hdac_bus_remove_device(struct hdac_bus *bus, | ||
171 | struct hdac_device *codec) | ||
172 | { | ||
173 | WARN_ON(bus != codec->bus); | ||
174 | if (list_empty(&codec->list)) | ||
175 | return; | ||
176 | list_del_init(&codec->list); | ||
177 | bus->caddr_tbl[codec->addr] = NULL; | ||
178 | clear_bit(codec->addr, &bus->codec_powered); | ||
179 | bus->num_codecs--; | ||
180 | } | ||
181 | EXPORT_SYMBOL_GPL(snd_hdac_bus_remove_device); | ||