⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 soc-dapm.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 3 页
字号:
/* * soc-dapm.c  --  ALSA SoC Dynamic Audio Power Management * * Copyright 2005 Wolfson Microelectronics PLC. * Author: Liam Girdwood *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com * *  This program is free software; you can redistribute  it and/or modify it *  under  the terms of  the GNU General  Public License as published by the *  Free Software Foundation;  either version 2 of the  License, or (at your *  option) any later version. * *  Revision history *    12th Aug 2005   Initial version. *    25th Oct 2005   Implemented path power domain. *    18th Dec 2005   Implemented machine and stream level power domain. * *  Features: *    o Changes power status of internal codec blocks depending on the *      dynamic configuration of codec internal audio paths and active *      DAC's/ADC's. *    o Platform power domain - can support external components i.e. amps and *      mic/meadphone insertion events. *    o Automatic Mic Bias support *    o Jack insertion power event initiation - e.g. hp insertion will enable *      sinks, dacs, etc *    o Delayed powerdown of audio susbsystem to reduce pops between a quick *      device reopen. * *  Todo: *    o DAPM power change sequencing - allow for configurable per *      codec sequences. *    o Support for analogue bias optimisation. *    o Support for reduced codec oversampling rates. *    o Support for reduced codec bias currents. */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/bitops.h>#include <linux/platform_device.h>#include <linux/jiffies.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/soc-dapm.h>#include <sound/initval.h>/* debug */#define DAPM_DEBUG 0#if DAPM_DEBUG#define dump_dapm(codec, action) dbg_dump_dapm(codec, action)#define dbg(format, arg...) printk(format, ## arg)#else#define dump_dapm(codec, action)#define dbg(format, arg...)#endif#define POP_DEBUG 0#if POP_DEBUG#define POP_TIME 500 /* 500 msecs - change if pop debug is too fast */#define pop_wait(time) schedule_timeout_uninterruptible(msecs_to_jiffies(time))#define pop_dbg(format, arg...) printk(format, ## arg); pop_wait(POP_TIME)#else#define pop_dbg(format, arg...)#define pop_wait(time)#endif/* dapm power sequences - make this per codec in the future */static int dapm_up_seq[] = {	snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic,	snd_soc_dapm_mux, snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_pga,	snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post};static int dapm_down_seq[] = {	snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,	snd_soc_dapm_pga, snd_soc_dapm_mixer, snd_soc_dapm_dac, snd_soc_dapm_mic,	snd_soc_dapm_micbias, snd_soc_dapm_mux, snd_soc_dapm_post};static int dapm_status = 1;module_param(dapm_status, int, 0);MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries");/* create a new dapm widget */static inline struct snd_soc_dapm_widget *dapm_cnew_widget(	const struct snd_soc_dapm_widget *_widget){	return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL);}/* set up initial codec paths */static void dapm_set_path_status(struct snd_soc_dapm_widget *w,	struct snd_soc_dapm_path *p, int i){	switch (w->id) {	case snd_soc_dapm_switch:	case snd_soc_dapm_mixer: {		int val;		int reg = w->kcontrols[i].private_value & 0xff;		int shift = (w->kcontrols[i].private_value >> 8) & 0x0f;		int mask = (w->kcontrols[i].private_value >> 16) & 0xff;		int invert = (w->kcontrols[i].private_value >> 24) & 0x01;		val = snd_soc_read(w->codec, reg);		val = (val >> shift) & mask;		if ((invert && !val) || (!invert && val))			p->connect = 1;		else			p->connect = 0;	}	break;	case snd_soc_dapm_mux: {		struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value;		int val, item, bitmask;		for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)		;		val = snd_soc_read(w->codec, e->reg);		item = (val >> e->shift_l) & (bitmask - 1);		p->connect = 0;		for (i = 0; i < e->mask; i++) {			if (!(strcmp(p->name, e->texts[i])) && item == i)				p->connect = 1;		}	}	break;	/* does not effect routing - always connected */	case snd_soc_dapm_pga:	case snd_soc_dapm_output:	case snd_soc_dapm_adc:	case snd_soc_dapm_input:	case snd_soc_dapm_dac:	case snd_soc_dapm_micbias:	case snd_soc_dapm_vmid:		p->connect = 1;	break;	/* does effect routing - dynamically connected */	case snd_soc_dapm_hp:	case snd_soc_dapm_mic:	case snd_soc_dapm_spk:	case snd_soc_dapm_line:	case snd_soc_dapm_pre:	case snd_soc_dapm_post:		p->connect = 0;	break;	}}/* connect mux widget to it's interconnecting audio paths */static int dapm_connect_mux(struct snd_soc_codec *codec,	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,	struct snd_soc_dapm_path *path, const char *control_name,	const struct snd_kcontrol_new *kcontrol){	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;	int i;	for (i = 0; i < e->mask; i++) {		if (!(strcmp(control_name, e->texts[i]))) {			list_add(&path->list, &codec->dapm_paths);			list_add(&path->list_sink, &dest->sources);			list_add(&path->list_source, &src->sinks);			path->name = (char*)e->texts[i];			dapm_set_path_status(dest, path, 0);			return 0;		}	}	return -ENODEV;}/* connect mixer widget to it's interconnecting audio paths */static int dapm_connect_mixer(struct snd_soc_codec *codec,	struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,	struct snd_soc_dapm_path *path, const char *control_name){	int i;	/* search for mixer kcontrol */	for (i = 0; i < dest->num_kcontrols; i++) {		if (!strcmp(control_name, dest->kcontrols[i].name)) {			list_add(&path->list, &codec->dapm_paths);			list_add(&path->list_sink, &dest->sources);			list_add(&path->list_source, &src->sinks);			path->name = dest->kcontrols[i].name;			dapm_set_path_status(dest, path, i);			return 0;		}	}	return -ENODEV;}/* update dapm codec register bits */static int dapm_update_bits(struct snd_soc_dapm_widget *widget){	int change, power;	unsigned short old, new;	struct snd_soc_codec *codec = widget->codec;	/* check for valid widgets */	if (widget->reg < 0 || widget->id == snd_soc_dapm_input ||		widget->id == snd_soc_dapm_output ||		widget->id == snd_soc_dapm_hp ||		widget->id == snd_soc_dapm_mic ||		widget->id == snd_soc_dapm_line ||		widget->id == snd_soc_dapm_spk)		return 0;	power = widget->power;	if (widget->invert)		power = (power ? 0:1);	old = snd_soc_read(codec, widget->reg);	new = (old & ~(0x1 << widget->shift)) | (power << widget->shift);	change = old != new;	if (change) {		pop_dbg("pop test %s : %s in %d ms\n", widget->name,			widget->power ? "on" : "off", POP_TIME);		snd_soc_write(codec, widget->reg, new);		pop_wait(POP_TIME);	}	dbg("reg old %x new %x change %d\n", old, new, change);	return change;}/* ramps the volume up or down to minimise pops before or after a * DAPM power event */static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power){	const struct snd_kcontrol_new *k = widget->kcontrols;	if (widget->muted && !power)		return 0;	if (!widget->muted && power)		return 0;	if (widget->num_kcontrols && k) {		int reg = k->private_value & 0xff;		int shift = (k->private_value >> 8) & 0x0f;		int mask = (k->private_value >> 16) & 0xff;		int invert = (k->private_value >> 24) & 0x01;		if (power) {			int i;			/* power up has happended, increase volume to last level */			if (invert) {				for (i = mask; i > widget->saved_value; i--)					snd_soc_update_bits(widget->codec, reg, mask, i);			} else {				for (i = 0; i < widget->saved_value; i++)					snd_soc_update_bits(widget->codec, reg, mask, i);			}			widget->muted = 0;		} else {			/* power down is about to occur, decrease volume to mute */			int val = snd_soc_read(widget->codec, reg);			int i = widget->saved_value = (val >> shift) & mask;			if (invert) {				for (; i < mask; i++)					snd_soc_update_bits(widget->codec, reg, mask, i);			} else {				for (; i > 0; i--)					snd_soc_update_bits(widget->codec, reg, mask, i);			}			widget->muted = 1;		}	}	return 0;}/* create new dapm mixer control */static int dapm_new_mixer(struct snd_soc_codec *codec,	struct snd_soc_dapm_widget *w){	int i, ret = 0;	char name[32];	struct snd_soc_dapm_path *path;	/* add kcontrol */	for (i = 0; i < w->num_kcontrols; i++) {		/* match name */		list_for_each_entry(path, &w->sources, list_sink) {			/* mixer/mux paths name must match control name */			if (path->name != (char*)w->kcontrols[i].name)				continue;			/* add dapm control with long name */			snprintf(name, 32, "%s %s", w->name, w->kcontrols[i].name);			path->long_name = kstrdup (name, GFP_KERNEL);			if (path->long_name == NULL)				return -ENOMEM;			path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w,				path->long_name);			ret = snd_ctl_add(codec->card, path->kcontrol);			if (ret < 0) {				printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n",						path->long_name);				kfree(path->long_name);				path->long_name = NULL;				return ret;			}		}	}	return ret;}/* create new dapm mux control */static int dapm_new_mux(struct snd_soc_codec *codec,	struct snd_soc_dapm_widget *w){	struct snd_soc_dapm_path *path = NULL;	struct snd_kcontrol *kcontrol;	int ret = 0;	if (!w->num_kcontrols) {		printk(KERN_ERR "asoc: mux %s has no controls\n", w->name);		return -EINVAL;	}	kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);	ret = snd_ctl_add(codec->card, kcontrol);	if (ret < 0)		goto err;	list_for_each_entry(path, &w->sources, list_sink)		path->kcontrol = kcontrol;	return ret;err:	printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);	return ret;}/* create new dapm volume control */static int dapm_new_pga(struct snd_soc_codec *codec,	struct snd_soc_dapm_widget *w){	struct snd_kcontrol *kcontrol;	int ret = 0;	if (!w->num_kcontrols)		return -EINVAL;	kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);	ret = snd_ctl_add(codec->card, kcontrol);	if (ret < 0) {		printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);		return ret;	}	return ret;}/* reset 'walked' bit for each dapm path */static inline void dapm_clear_walk(struct snd_soc_codec *codec){	struct snd_soc_dapm_path *p;	list_for_each_entry(p, &codec->dapm_paths, list)		p->walked = 0;}/* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. */static int is_connected_output_ep(struct snd_soc_dapm_widget *widget){	struct snd_soc_dapm_path *path;	int con = 0;	if (widget->id == snd_soc_dapm_adc && widget->active)		return 1;	if (widget->connected) {		/* connected pin ? */		if (widget->id == snd_soc_dapm_output && !widget->ext)			return 1;		/* connected jack or spk ? */		if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||			widget->id == snd_soc_dapm_line)			return 1;	}	list_for_each_entry(path, &widget->sinks, list_source) {		if (path->walked)			continue;		if (path->sink && path->connect) {			path->walked = 1;			con += is_connected_output_ep(path->sink);		}	}	return con;}/* * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. */static int is_connected_input_ep(struct snd_soc_dapm_widget *widget){	struct snd_soc_dapm_path *path;	int con = 0;	/* active stream ? */	if (widget->id == snd_soc_dapm_dac && widget->active)		return 1;	if (widget->connected) {		/* connected pin ? */		if (widget->id == snd_soc_dapm_input && !widget->ext)			return 1;		/* connected VMID/Bias for lower pops */		if (widget->id == snd_soc_dapm_vmid)			return 1;		/* connected jack ? */		if (widget->id == snd_soc_dapm_mic || widget->id == snd_soc_dapm_line)			return 1;	}	list_for_each_entry(path, &widget->sources, list_sink) {		if (path->walked)			continue;		if (path->source && path->connect) {

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -