📄 soc-dapm.c
字号:
/* * 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 + -