@@ -122,6 +122,7 @@ struct snd_card {
size_t total_pcm_alloc_bytes; /* total amount of allocated buffers */
struct mutex memory_mutex; /* protection for the above */
+ bool jack_inject_attr_registered; /* jack software inject sysfs interface registered? */
#ifdef CONFIG_PM
unsigned int power_state; /* power state */
@@ -67,6 +67,7 @@ struct snd_jack {
char name[100];
unsigned int key[6]; /* Keep in sync with definitions above */
#endif /* CONFIG_SND_JACK_INPUT_DEV */
+ bool sw_inject_enable;
void *private_data;
void (*private_free)(struct snd_jack *);
};
@@ -8,6 +8,7 @@
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/ctype.h>
#include <sound/jack.h>
#include <sound/core.h>
#include <sound/control.h>
@@ -180,6 +181,123 @@ int snd_jack_add_new_kctl(struct snd_jack *jack, const char * name, int mask)
}
EXPORT_SYMBOL(snd_jack_add_new_kctl);
+static struct snd_jack *parsing_jack_and_enable(struct snd_card *card, const char *buf,
+ size_t count, unsigned long *enable)
+{
+ struct snd_device *sdev;
+ struct snd_jack *jack = NULL;
+ bool jack_found = 0;
+ char *jackid, *ena;
+ int i, err;
+
+ /* skip the '\n\r' at the end of buf */
+ for (i = count - 2; i > 0; i--)
+ if (isspace(buf[i]))
+ break;
+ if (i == 0)
+ return NULL;
+
+ jackid = kstrndup(buf, i, GFP_KERNEL);
+ if (!jackid)
+ return NULL;
+
+ if (strstr(jackid, "Phantom"))
+ goto exit1;
+
+ ena = kstrndup(&buf[i+1], count - i - 2, GFP_KERNEL);
+ if (!ena)
+ goto exit1;
+
+ err = kstrtoul(ena, 0, enable);
+ if (err)
+ goto exit;
+
+ list_for_each_entry(sdev, &card->devices, list)
+ if (sdev->type == SNDRV_DEV_JACK) {
+ jack = (struct snd_jack *) sdev->device_data;
+ if (!strcmp(jack->id, jackid)) {
+ jack_found = 1;
+ break;
+ }
+ }
+
+ if (!jack_found)
+ jack = NULL;
+ exit:
+ kfree(ena);
+ exit1:
+ kfree(jackid);
+ return jack;
+}
+
+static ssize_t
+jackin_inject_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_card *card = container_of(dev, struct snd_card, card_dev);
+ struct snd_jack *jack;
+ unsigned long enable;
+
+ jack = parsing_jack_and_enable(card, buf, count, &enable);
+ if (!jack)
+ return -EINVAL;
+
+ if (jack->sw_inject_enable)
+ snd_jack_report(jack, enable ? jack->type : 0, true);
+
+ return count;
+}
+static DEVICE_ATTR_WO(jackin_inject);
+
+static ssize_t
+sw_inject_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_card *card = container_of(dev, struct snd_card, card_dev);
+ struct snd_device *sdev;
+ struct snd_jack *jack = NULL;
+ ssize_t ret_count = 0;
+
+ list_for_each_entry(sdev, &card->devices, list)
+ if (sdev->type == SNDRV_DEV_JACK) {
+ jack = (struct snd_jack *) sdev->device_data;
+ if (strstr(jack->id, "Phantom"))
+ continue;
+ ret_count += scnprintf(buf + ret_count, PAGE_SIZE, "%s: %s %i\n",
+ "Jack", jack->id, jack->sw_inject_enable);
+ }
+ return ret_count;
+}
+
+static ssize_t
+sw_inject_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct snd_card *card = container_of(dev, struct snd_card, card_dev);
+ struct snd_jack *jack;
+ unsigned long enable;
+
+ jack = parsing_jack_and_enable(card, buf, count, &enable);
+ if (!jack)
+ return -EINVAL;
+
+ jack->sw_inject_enable = !!enable;
+
+ return count;
+}
+static DEVICE_ATTR_RW(sw_inject_enable);
+
+static struct attribute *snd_jack_dev_attrs[] = {
+ &dev_attr_jackin_inject.attr,
+ &dev_attr_sw_inject_enable.attr,
+ NULL
+};
+
+static const struct attribute_group snd_jack_dev_attr_group = {
+ .name = "jack",
+ .attrs = snd_jack_dev_attrs,
+};
+
/**
* snd_jack_new - Create a new jack
* @card: the card instance
@@ -256,6 +374,11 @@ int snd_jack_new(struct snd_card *card, const char *id, int type,
*jjack = jack;
+ if (!jack->card->jack_inject_attr_registered) {
+ jack->card->jack_inject_attr_registered = true;
+ snd_card_add_dev_attr(jack->card, &snd_jack_dev_attr_group);
+ }
+
return 0;
fail_input:
@@ -348,6 +471,9 @@ void snd_jack_report(struct snd_jack *jack, int status, bool sw_inject)
if (!jack)
return;
+ if (jack->sw_inject_enable && !sw_inject)
+ return;
+
list_for_each_entry(jack_kctl, &jack->kctl_list, list)
snd_kctl_jack_report(jack->card, jack_kctl->kctl,
status & jack_kctl->mask_bits);
We want to perform remote audio auto test, need the audio jack to change from plugout to plugin or vice versa by software ways. Here the design is if the sound card has at least one Jack, the kernel will build a sysfs interface of jack injection for this sound card, it will create 2 files: jackin_inject and sw_inject_enable users need to cat sw_inject_enable first to check all jacks and their injection enable status, like below (0 means disabled): Jack: Mic 0 Jack: Headphone 0 Jack: HDMI/DP,pcm=3 0 Jack: HDMI/DP,pcm=4 0 Jack: HDMI/DP,pcm=5 0 Suppose users want to enable jack injection for Headphone, they need to run $sudo sh -c 'echo Headphone 1 > sw_inject_enable', then users could change the Headphone Jack state through jackin_inject and this Jack's state will not changed by non-injection ways anymore. Users could run $sudo sh -c 'echo Headphone 1 > jackin_inject' to trigger the Headphone jack to plugin or echo Headphone 0 to trigger it to plugout. If users finish their test, they could run $sudo sh -c 'echo Headphone 0 > sw_inject_enable' to disable injection and let non-injection ways control this Jack. Signed-off-by: Hui Wang <hui.wang@canonical.com> --- include/sound/core.h | 1 + include/sound/jack.h | 1 + sound/core/jack.c | 126 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+)