From 420c0117db25db38b72b6230223f7a976d3070ea Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sun, 17 Jun 2018 19:02:04 +0200 Subject: dmaengine: pxa: use a dma slave map In order to remove the specific knowledge of the dma mapping from PXA drivers, add a default slave map for pxa architectures. This won't impact MMP architecture, but is aimed only at all PXA boards. This is the first step, and once all drivers are converted, pxad_filter_fn() will be made static, and the DMA resources removed from device.c. Signed-off-by: Robert Jarzmik Reported-by: Arnd Bergmann Acked-by: Vinod Koul --- include/linux/platform_data/mmp_dma.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include/linux') diff --git a/include/linux/platform_data/mmp_dma.h b/include/linux/platform_data/mmp_dma.h index d1397c8ed94e..6397b9c8149a 100644 --- a/include/linux/platform_data/mmp_dma.h +++ b/include/linux/platform_data/mmp_dma.h @@ -12,9 +12,13 @@ #ifndef MMP_DMA_H #define MMP_DMA_H +struct dma_slave_map; + struct mmp_dma_platdata { int dma_channels; int nb_requestors; + int slave_map_cnt; + const struct dma_slave_map *slave_map; }; #endif /* MMP_DMA_H */ -- cgit v1.2.3-71-gd317 From b6d1a17f4729e4fda5740a855da91d202db2c118 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sun, 17 Jun 2018 19:02:14 +0200 Subject: dmaengine: pxa: document pxad_param Add some documentation for the pxad_param structure, and describe the contract behind the minimal required priority of a DMA channel. Signed-off-by: Robert Jarzmik Acked-by: Vinod Koul --- include/linux/dma/pxa-dma.h | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'include/linux') diff --git a/include/linux/dma/pxa-dma.h b/include/linux/dma/pxa-dma.h index e56ec7af4fd7..9fc594f69eff 100644 --- a/include/linux/dma/pxa-dma.h +++ b/include/linux/dma/pxa-dma.h @@ -9,6 +9,15 @@ enum pxad_chan_prio { PXAD_PRIO_LOWEST, }; +/** + * struct pxad_param - dma channel request parameters + * @drcmr: requestor line number + * @prio: minimal mandatory priority of the channel + * + * If a requested channel is granted, its priority will be at least @prio, + * ie. if PXAD_PRIO_LOW is required, the requested channel will be either + * PXAD_PRIO_LOW, PXAD_PRIO_NORMAL or PXAD_PRIO_HIGHEST. + */ struct pxad_param { unsigned int drcmr; enum pxad_chan_prio prio; -- cgit v1.2.3-71-gd317 From cd31b80736852d34bc1072f3e579a6fd73a244e7 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sun, 17 Jun 2018 19:02:17 +0200 Subject: ARM: pxa: change SSP DMA channels allocation Now the dma_slave_map is available for PXA architecture, switch the SSP device to it. This specifically means that : - for platform data based machines, the DMA requestor channels are extracted from the slave map, where pxa-ssp-dai. is a 1-1 match to ssp., and the channels are either "rx" or "tx". - for device tree platforms, the dma node should be hooked into the pxa2xx-ac97 or pxa-ssp-dai node. Signed-off-by: Robert Jarzmik Acked-by: Daniel Mack --- arch/arm/plat-pxa/ssp.c | 47 ---------------------------------------------- include/linux/pxa2xx_ssp.h | 2 -- sound/soc/pxa/pxa-ssp.c | 5 ++--- 3 files changed, 2 insertions(+), 52 deletions(-) (limited to 'include/linux') diff --git a/arch/arm/plat-pxa/ssp.c b/arch/arm/plat-pxa/ssp.c index ba13f793fbce..ed36dcab80f1 100644 --- a/arch/arm/plat-pxa/ssp.c +++ b/arch/arm/plat-pxa/ssp.c @@ -127,53 +127,6 @@ static int pxa_ssp_probe(struct platform_device *pdev) if (IS_ERR(ssp->clk)) return PTR_ERR(ssp->clk); - if (dev->of_node) { - struct of_phandle_args dma_spec; - struct device_node *np = dev->of_node; - int ret; - - /* - * FIXME: we should allocate the DMA channel from this - * context and pass the channel down to the ssp users. - * For now, we lookup the rx and tx indices manually - */ - - /* rx */ - ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", - 0, &dma_spec); - - if (ret) { - dev_err(dev, "Can't parse dmas property\n"); - return -ENODEV; - } - ssp->drcmr_rx = dma_spec.args[0]; - of_node_put(dma_spec.np); - - /* tx */ - ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells", - 1, &dma_spec); - if (ret) { - dev_err(dev, "Can't parse dmas property\n"); - return -ENODEV; - } - ssp->drcmr_tx = dma_spec.args[0]; - of_node_put(dma_spec.np); - } else { - res = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (res == NULL) { - dev_err(dev, "no SSP RX DRCMR defined\n"); - return -ENODEV; - } - ssp->drcmr_rx = res->start; - - res = platform_get_resource(pdev, IORESOURCE_DMA, 1); - if (res == NULL) { - dev_err(dev, "no SSP TX DRCMR defined\n"); - return -ENODEV; - } - ssp->drcmr_tx = res->start; - } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(dev, "no memory resource defined\n"); diff --git a/include/linux/pxa2xx_ssp.h b/include/linux/pxa2xx_ssp.h index 8461b18e4608..03a7ca46735b 100644 --- a/include/linux/pxa2xx_ssp.h +++ b/include/linux/pxa2xx_ssp.h @@ -212,8 +212,6 @@ struct ssp_device { int type; int use_count; int irq; - int drcmr_rx; - int drcmr_tx; struct device_node *of_node; }; diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 6fc986080130..0b441338bdd4 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -105,9 +105,8 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, dma = kzalloc(sizeof(struct snd_dmaengine_dai_dma_data), GFP_KERNEL); if (!dma) return -ENOMEM; - - dma->filter_data = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? - &ssp->drcmr_tx : &ssp->drcmr_rx; + dma->chan_name = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "tx" : "rx"; snd_soc_dai_set_dma_data(cpu_dai, substream, dma); -- cgit v1.2.3-71-gd317 From 9fba738a53dda20e748d6ee240b6c017c8146b4b Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Tue, 19 Jun 2018 16:41:41 +0200 Subject: clk: add duty cycle support Add the possibility to apply and query the clock signal duty cycle ratio. This is useful when the duty cycle of the clock signal depends on some other parameters controlled by the clock framework. For example, the duty cycle of a divider may depends on the raw divider setting (ratio = N / div) , which is controlled by the CCF. In such case, going through the pwm framework to control the duty cycle ratio of this clock would be a burden. A clock provider is not required to implement the operation to set and get the duty cycle. If it does not implement .get_duty_cycle(), the ratio is assumed to be 50%. This change also adds a new flag, CLK_DUTY_CYCLE_PARENT. This flag should be used to indicate that a clock, such as gates and muxes, may inherit the duty cycle ratio of its parent clock. If a clock does not provide a get_duty_cycle() callback and has CLK_DUTY_CYCLE_PARENT, then the call will be directly forwarded to its parent clock, if any. For set_duty_cycle(), the clock should also have CLK_SET_RATE_PARENT for the call to be forwarded Signed-off-by: Jerome Brunet Signed-off-by: Michael Turquette Link: lkml.kernel.org/r/20180619144141.8506-1-jbrunet@baylibre.com --- drivers/clk/clk.c | 199 +++++++++++++++++++++++++++++++++++++++++-- include/linux/clk-provider.h | 26 ++++++ include/linux/clk.h | 33 +++++++ include/trace/events/clk.h | 36 ++++++++ 4 files changed, 289 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 9760b526ca31..b0a2719d86f3 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -68,6 +68,7 @@ struct clk_core { unsigned long max_rate; unsigned long accuracy; int phase; + struct clk_duty duty; struct hlist_head children; struct hlist_node child_node; struct hlist_head clks; @@ -2402,6 +2403,172 @@ int clk_get_phase(struct clk *clk) } EXPORT_SYMBOL_GPL(clk_get_phase); +static void clk_core_reset_duty_cycle_nolock(struct clk_core *core) +{ + /* Assume a default value of 50% */ + core->duty.num = 1; + core->duty.den = 2; +} + +static int clk_core_update_duty_cycle_parent_nolock(struct clk_core *core); + +static int clk_core_update_duty_cycle_nolock(struct clk_core *core) +{ + struct clk_duty *duty = &core->duty; + int ret = 0; + + if (!core->ops->get_duty_cycle) + return clk_core_update_duty_cycle_parent_nolock(core); + + ret = core->ops->get_duty_cycle(core->hw, duty); + if (ret) + goto reset; + + /* Don't trust the clock provider too much */ + if (duty->den == 0 || duty->num > duty->den) { + ret = -EINVAL; + goto reset; + } + + return 0; + +reset: + clk_core_reset_duty_cycle_nolock(core); + return ret; +} + +static int clk_core_update_duty_cycle_parent_nolock(struct clk_core *core) +{ + int ret = 0; + + if (core->parent && + core->flags & CLK_DUTY_CYCLE_PARENT) { + ret = clk_core_update_duty_cycle_nolock(core->parent); + memcpy(&core->duty, &core->parent->duty, sizeof(core->duty)); + } else { + clk_core_reset_duty_cycle_nolock(core); + } + + return ret; +} + +static int clk_core_set_duty_cycle_parent_nolock(struct clk_core *core, + struct clk_duty *duty); + +static int clk_core_set_duty_cycle_nolock(struct clk_core *core, + struct clk_duty *duty) +{ + int ret; + + lockdep_assert_held(&prepare_lock); + + if (clk_core_rate_is_protected(core)) + return -EBUSY; + + trace_clk_set_duty_cycle(core, duty); + + if (!core->ops->set_duty_cycle) + return clk_core_set_duty_cycle_parent_nolock(core, duty); + + ret = core->ops->set_duty_cycle(core->hw, duty); + if (!ret) + memcpy(&core->duty, duty, sizeof(*duty)); + + trace_clk_set_duty_cycle_complete(core, duty); + + return ret; +} + +static int clk_core_set_duty_cycle_parent_nolock(struct clk_core *core, + struct clk_duty *duty) +{ + int ret = 0; + + if (core->parent && + core->flags & (CLK_DUTY_CYCLE_PARENT | CLK_SET_RATE_PARENT)) { + ret = clk_core_set_duty_cycle_nolock(core->parent, duty); + memcpy(&core->duty, &core->parent->duty, sizeof(core->duty)); + } + + return ret; +} + +/** + * clk_set_duty_cycle - adjust the duty cycle ratio of a clock signal + * @clk: clock signal source + * @num: numerator of the duty cycle ratio to be applied + * @den: denominator of the duty cycle ratio to be applied + * + * Apply the duty cycle ratio if the ratio is valid and the clock can + * perform this operation + * + * Returns (0) on success, a negative errno otherwise. + */ +int clk_set_duty_cycle(struct clk *clk, unsigned int num, unsigned int den) +{ + int ret; + struct clk_duty duty; + + if (!clk) + return 0; + + /* sanity check the ratio */ + if (den == 0 || num > den) + return -EINVAL; + + duty.num = num; + duty.den = den; + + clk_prepare_lock(); + + if (clk->exclusive_count) + clk_core_rate_unprotect(clk->core); + + ret = clk_core_set_duty_cycle_nolock(clk->core, &duty); + + if (clk->exclusive_count) + clk_core_rate_protect(clk->core); + + clk_prepare_unlock(); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_duty_cycle); + +static int clk_core_get_scaled_duty_cycle(struct clk_core *core, + unsigned int scale) +{ + struct clk_duty *duty = &core->duty; + int ret; + + clk_prepare_lock(); + + ret = clk_core_update_duty_cycle_nolock(core); + if (!ret) + ret = mult_frac(scale, duty->num, duty->den); + + clk_prepare_unlock(); + + return ret; +} + +/** + * clk_get_scaled_duty_cycle - return the duty cycle ratio of a clock signal + * @clk: clock signal source + * @scale: scaling factor to be applied to represent the ratio as an integer + * + * Returns the duty cycle ratio of a clock node multiplied by the provided + * scaling factor, or negative errno on error. + */ +int clk_get_scaled_duty_cycle(struct clk *clk, unsigned int scale) +{ + if (!clk) + return 0; + + return clk_core_get_scaled_duty_cycle(clk->core, scale); +} +EXPORT_SYMBOL_GPL(clk_get_scaled_duty_cycle); + /** * clk_is_match - check if two clk's point to the same hardware clock * @p: clk compared against q @@ -2455,12 +2622,13 @@ static void clk_summary_show_one(struct seq_file *s, struct clk_core *c, if (!c) return; - seq_printf(s, "%*s%-*s %7d %8d %8d %11lu %10lu %-3d\n", + seq_printf(s, "%*s%-*s %7d %8d %8d %11lu %10lu %5d %6d\n", level * 3 + 1, "", 30 - level * 3, c->name, c->enable_count, c->prepare_count, c->protect_count, clk_core_get_rate(c), clk_core_get_accuracy(c), - clk_core_get_phase(c)); + clk_core_get_phase(c), + clk_core_get_scaled_duty_cycle(c, 100000)); } static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c, @@ -2482,9 +2650,9 @@ static int clk_summary_show(struct seq_file *s, void *data) struct clk_core *c; struct hlist_head **lists = (struct hlist_head **)s->private; - seq_puts(s, " enable prepare protect \n"); - seq_puts(s, " clock count count count rate accuracy phase\n"); - seq_puts(s, "----------------------------------------------------------------------------------------\n"); + seq_puts(s, " enable prepare protect duty\n"); + seq_puts(s, " clock count count count rate accuracy phase cycle\n"); + seq_puts(s, "---------------------------------------------------------------------------------------------\n"); clk_prepare_lock(); @@ -2511,6 +2679,8 @@ static void clk_dump_one(struct seq_file *s, struct clk_core *c, int level) seq_printf(s, "\"rate\": %lu,", clk_core_get_rate(c)); seq_printf(s, "\"accuracy\": %lu,", clk_core_get_accuracy(c)); seq_printf(s, "\"phase\": %d", clk_core_get_phase(c)); + seq_printf(s, "\"duty_cycle\": %u", + clk_core_get_scaled_duty_cycle(c, 100000)); } static void clk_dump_subtree(struct seq_file *s, struct clk_core *c, int level) @@ -2572,6 +2742,7 @@ static const struct { ENTRY(CLK_SET_RATE_UNGATE), ENTRY(CLK_IS_CRITICAL), ENTRY(CLK_OPS_PARENT_ENABLE), + ENTRY(CLK_DUTY_CYCLE_PARENT), #undef ENTRY }; @@ -2610,6 +2781,17 @@ static int possible_parents_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(possible_parents); +static int clk_duty_cycle_show(struct seq_file *s, void *data) +{ + struct clk_core *core = s->private; + struct clk_duty *duty = &core->duty; + + seq_printf(s, "%u/%u\n", duty->num, duty->den); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(clk_duty_cycle); + static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *root; @@ -2628,6 +2810,8 @@ static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) debugfs_create_u32("clk_enable_count", 0444, root, &core->enable_count); debugfs_create_u32("clk_protect_count", 0444, root, &core->protect_count); debugfs_create_u32("clk_notifier_count", 0444, root, &core->notifier_count); + debugfs_create_file("clk_duty_cycle", 0444, root, core, + &clk_duty_cycle_fops); if (core->num_parents > 1) debugfs_create_file("clk_possible_parents", 0444, root, core, @@ -2845,6 +3029,11 @@ static int __clk_core_init(struct clk_core *core) else core->phase = 0; + /* + * Set clk's duty cycle. + */ + clk_core_update_duty_cycle_nolock(core); + /* * Set clk's rate. The preferred method is to use .recalc_rate. For * simple clocks and lazy developers the default fallback is to use the diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index b7cfa037e593..08b1aa70a38d 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -38,6 +38,8 @@ #define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */ /* parents need enable during gate/ungate, set rate and re-parent */ #define CLK_OPS_PARENT_ENABLE BIT(12) +/* duty cycle call may be forwarded to the parent clock */ +#define CLK_DUTY_CYCLE_PARENT BIT(13) struct clk; struct clk_hw; @@ -66,6 +68,17 @@ struct clk_rate_request { struct clk_hw *best_parent_hw; }; +/** + * struct clk_duty - Struture encoding the duty cycle ratio of a clock + * + * @num: Numerator of the duty cycle ratio + * @den: Denominator of the duty cycle ratio + */ +struct clk_duty { + unsigned int num; + unsigned int den; +}; + /** * struct clk_ops - Callback operations for hardware clocks; these are to * be provided by the clock implementation, and will be called by drivers @@ -169,6 +182,15 @@ struct clk_rate_request { * by the second argument. Valid values for degrees are * 0-359. Return 0 on success, otherwise -EERROR. * + * @get_duty_cycle: Queries the hardware to get the current duty cycle ratio + * of a clock. Returned values denominator cannot be 0 and must be + * superior or equal to the numerator. + * + * @set_duty_cycle: Apply the duty cycle ratio to this clock signal specified by + * the numerator (2nd argurment) and denominator (3rd argument). + * Argument must be a valid ratio (denominator > 0 + * and >= numerator) Return 0 on success, otherwise -EERROR. + * * @init: Perform platform-specific initialization magic. * This is not not used by any of the basic clock types. * Please consider other ways of solving initialization problems @@ -218,6 +240,10 @@ struct clk_ops { unsigned long parent_accuracy); int (*get_phase)(struct clk_hw *hw); int (*set_phase)(struct clk_hw *hw, int degrees); + int (*get_duty_cycle)(struct clk_hw *hw, + struct clk_duty *duty); + int (*set_duty_cycle)(struct clk_hw *hw, + struct clk_duty *duty); void (*init)(struct clk_hw *hw); void (*debug_init)(struct clk_hw *hw, struct dentry *dentry); }; diff --git a/include/linux/clk.h b/include/linux/clk.h index 0dbd0885b2c2..4f750c481b82 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -141,6 +141,27 @@ int clk_set_phase(struct clk *clk, int degrees); */ int clk_get_phase(struct clk *clk); +/** + * clk_set_duty_cycle - adjust the duty cycle ratio of a clock signal + * @clk: clock signal source + * @num: numerator of the duty cycle ratio to be applied + * @den: denominator of the duty cycle ratio to be applied + * + * Adjust the duty cycle of a clock signal by the specified ratio. Returns 0 on + * success, -EERROR otherwise. + */ +int clk_set_duty_cycle(struct clk *clk, unsigned int num, unsigned int den); + +/** + * clk_get_duty_cycle - return the duty cycle ratio of a clock signal + * @clk: clock signal source + * @scale: scaling factor to be applied to represent the ratio as an integer + * + * Returns the duty cycle ratio multiplied by the scale provided, otherwise + * returns -EERROR. + */ +int clk_get_scaled_duty_cycle(struct clk *clk, unsigned int scale); + /** * clk_is_match - check if two clk's point to the same hardware clock * @p: clk compared against q @@ -183,6 +204,18 @@ static inline long clk_get_phase(struct clk *clk) return -ENOTSUPP; } +static inline int clk_set_duty_cycle(struct clk *clk, unsigned int num, + unsigned int den) +{ + return -ENOTSUPP; +} + +static inline unsigned int clk_get_scaled_duty_cycle(struct clk *clk, + unsigned int scale) +{ + return 0; +} + static inline bool clk_is_match(const struct clk *p, const struct clk *q) { return p == q; diff --git a/include/trace/events/clk.h b/include/trace/events/clk.h index 2cd449328aee..9004ffff7f32 100644 --- a/include/trace/events/clk.h +++ b/include/trace/events/clk.h @@ -192,6 +192,42 @@ DEFINE_EVENT(clk_phase, clk_set_phase_complete, TP_ARGS(core, phase) ); +DECLARE_EVENT_CLASS(clk_duty_cycle, + + TP_PROTO(struct clk_core *core, struct clk_duty *duty), + + TP_ARGS(core, duty), + + TP_STRUCT__entry( + __string( name, core->name ) + __field( unsigned int, num ) + __field( unsigned int, den ) + ), + + TP_fast_assign( + __assign_str(name, core->name); + __entry->num = duty->num; + __entry->den = duty->den; + ), + + TP_printk("%s %u/%u", __get_str(name), (unsigned int)__entry->num, + (unsigned int)__entry->den) +); + +DEFINE_EVENT(clk_duty_cycle, clk_set_duty_cycle, + + TP_PROTO(struct clk_core *core, struct clk_duty *duty), + + TP_ARGS(core, duty) +); + +DEFINE_EVENT(clk_duty_cycle, clk_set_duty_cycle_complete, + + TP_PROTO(struct clk_core *core, struct clk_duty *duty), + + TP_ARGS(core, duty) +); + #endif /* _TRACE_CLK_H */ /* This part must be outside protection */ -- cgit v1.2.3-71-gd317 From 05739375f1c0a1048fea8b9c4cb54d9e4a891938 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 29 Jun 2018 14:59:40 +0200 Subject: ASoC: pxa-ssp: remove .set_pll() and .set_clkdiv() callbacks The .set_pll() and .set_clkdiv() callbacks are considered legacy and should not be used anymore. In order to support PXA boards on DT platforms, remove them and let the code figure out the correct dividers and PLL base frequencies itself. Signed-off-by: Daniel Mack Signed-off-by: Mark Brown --- include/linux/pxa2xx_ssp.h | 8 +++ sound/soc/pxa/pxa-ssp.c | 146 ++++++++++++++++++++++----------------------- 2 files changed, 81 insertions(+), 73 deletions(-) (limited to 'include/linux') diff --git a/include/linux/pxa2xx_ssp.h b/include/linux/pxa2xx_ssp.h index 03a7ca46735b..13b4244d44c1 100644 --- a/include/linux/pxa2xx_ssp.h +++ b/include/linux/pxa2xx_ssp.h @@ -171,6 +171,14 @@ #define SSACD_SCDB (1 << 3) /* SSPSYSCLK Divider Bypass */ #define SSACD_ACPS(x) ((x) << 4) /* Audio clock PLL select */ #define SSACD_ACDS(x) ((x) << 0) /* Audio clock divider select */ +#define SSACD_ACDS_1 (0) +#define SSACD_ACDS_2 (1) +#define SSACD_ACDS_4 (2) +#define SSACD_ACDS_8 (3) +#define SSACD_ACDS_16 (4) +#define SSACD_ACDS_32 (5) +#define SSACD_SCDB_4X (0) +#define SSACD_SCDB_1X (1) #define SSACD_SCDX8 (1 << 7) /* SYSCLK division ratio select */ /* LPSS SSP */ diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 01d54697ede4..f8339bb01251 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -41,6 +41,7 @@ */ struct ssp_priv { struct ssp_device *ssp; + unsigned long ssp_clk; unsigned int sysclk; unsigned int dai_fmt; unsigned int configured_dai_fmt; @@ -192,21 +193,6 @@ static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div) pxa_ssp_write_reg(ssp, SSCR0, sscr0); } -/** - * pxa_ssp_get_clkdiv - get SSP clock divider - */ -static u32 pxa_ssp_get_scr(struct ssp_device *ssp) -{ - u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0); - u32 div; - - if (ssp->type == PXA25x_SSP) - div = ((sscr0 >> 8) & 0xff) * 2 + 2; - else - div = ((sscr0 >> 8) & 0xfff) + 1; - return div; -} - /* * Set the SSP ports SYSCLK. */ @@ -262,67 +248,18 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, return 0; } -/* - * Set the SSP clock dividers. - */ -static int pxa_ssp_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, - int div_id, int div) -{ - struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); - struct ssp_device *ssp = priv->ssp; - int val; - - switch (div_id) { - case PXA_SSP_AUDIO_DIV_ACDS: - val = (pxa_ssp_read_reg(ssp, SSACD) & ~0x7) | SSACD_ACDS(div); - pxa_ssp_write_reg(ssp, SSACD, val); - break; - case PXA_SSP_AUDIO_DIV_SCDB: - val = pxa_ssp_read_reg(ssp, SSACD); - val &= ~SSACD_SCDB; - if (ssp->type == PXA3xx_SSP) - val &= ~SSACD_SCDX8; - switch (div) { - case PXA_SSP_CLK_SCDB_1: - val |= SSACD_SCDB; - break; - case PXA_SSP_CLK_SCDB_4: - break; - case PXA_SSP_CLK_SCDB_8: - if (ssp->type == PXA3xx_SSP) - val |= SSACD_SCDX8; - else - return -EINVAL; - break; - default: - return -EINVAL; - } - pxa_ssp_write_reg(ssp, SSACD, val); - break; - case PXA_SSP_DIV_SCR: - pxa_ssp_set_scr(ssp, div); - break; - default: - return -ENODEV; - } - - return 0; -} - /* * Configure the PLL frequency pxa27x and (afaik - pxa320 only) */ -static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, - int source, unsigned int freq_in, unsigned int freq_out) +static int pxa_ssp_set_pll(struct ssp_priv *priv, unsigned int freq) { - struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); struct ssp_device *ssp = priv->ssp; u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70; if (ssp->type == PXA3xx_SSP) pxa_ssp_write_reg(ssp, SSACDD, 0); - switch (freq_out) { + switch (freq) { case 5622000: break; case 11345000: @@ -353,7 +290,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, u64 tmp = 19968; tmp *= 1000000; - do_div(tmp, freq_out); + do_div(tmp, freq); val = tmp; val = (val << 16) | 64; @@ -363,7 +300,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, dev_dbg(&ssp->pdev->dev, "Using SSACDD %x to supply %uHz\n", - val, freq_out); + val, freq); break; } @@ -568,6 +505,24 @@ static int pxa_ssp_configure_dai_fmt(struct ssp_priv *priv) return 0; } +struct pxa_ssp_clock_mode { + int rate; + int pll; + u8 acds; + u8 scdb; +}; + +static const struct pxa_ssp_clock_mode pxa_ssp_clock_modes[] = { + { .rate = 8000, .pll = 32842000, .acds = SSACD_ACDS_32, .scdb = SSACD_SCDB_4X }, + { .rate = 11025, .pll = 5622000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_4X }, + { .rate = 16000, .pll = 32842000, .acds = SSACD_ACDS_16, .scdb = SSACD_SCDB_4X }, + { .rate = 22050, .pll = 5622000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 44100, .pll = 11345000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 48000, .pll = 12235000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X }, + { .rate = 96000, .pll = 12235000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_1X }, + {} +}; + /* * Set the SSP audio DMA parameters and sample size. * Can be called multiple times by oss emulation. @@ -579,11 +534,12 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai); struct ssp_device *ssp = priv->ssp; int chn = params_channels(params); - u32 sscr0; - u32 sspsp; + u32 sscr0, sspsp; int width = snd_pcm_format_physical_width(params_format(params)); int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf; struct snd_dmaengine_dai_dma_data *dma_data; + int rate = params_rate(params); + int bclk = rate * chn * (width / 8); int ret; dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); @@ -623,11 +579,57 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, } pxa_ssp_write_reg(ssp, SSCR0, sscr0); + if (sscr0 & SSCR0_ACS) { + ret = pxa_ssp_set_pll(priv, bclk); + + /* + * If we were able to generate the bclk directly, + * all is fine. Otherwise, look up the closest rate + * from the table and also set the dividers. + */ + + if (ret < 0) { + const struct pxa_ssp_clock_mode *m; + int ssacd, acds; + + for (m = pxa_ssp_clock_modes; m->rate; m++) { + if (m->rate == rate) + break; + } + + if (!m->rate) + return -EINVAL; + + acds = m->acds; + + /* The values in the table are for 16 bits */ + if (width == 32) + acds--; + + ret = pxa_ssp_set_pll(priv, bclk); + if (ret < 0) + return ret; + + ssacd = pxa_ssp_read_reg(ssp, SSACD); + ssacd &= ~(SSACD_ACDS(7) | SSACD_SCDB_1X); + ssacd |= SSACD_ACDS(m->acds); + ssacd |= m->scdb; + pxa_ssp_write_reg(ssp, SSACD, ssacd); + } + } else if (sscr0 & SSCR0_ECS) { + /* + * For setups with external clocking, the PLL and its diviers + * are not active. Instead, the SCR bits in SSCR0 can be used + * to divide the clock. + */ + pxa_ssp_set_scr(ssp, bclk / rate); + } + switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: sspsp = pxa_ssp_read_reg(ssp, SSPSP); - if ((pxa_ssp_get_scr(ssp) == 4) && (width == 16)) { + if (((priv->sysclk / bclk) == 64) && (width == 16)) { /* This is a special case where the bitclk is 64fs * and we're not dealing with 2*32 bits of audio * samples. @@ -812,8 +814,6 @@ static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { .trigger = pxa_ssp_trigger, .hw_params = pxa_ssp_hw_params, .set_sysclk = pxa_ssp_set_dai_sysclk, - .set_clkdiv = pxa_ssp_set_dai_clkdiv, - .set_pll = pxa_ssp_set_dai_pll, .set_fmt = pxa_ssp_set_dai_fmt, .set_tdm_slot = pxa_ssp_set_dai_tdm_slot, .set_tristate = pxa_ssp_set_dai_tristate, -- cgit v1.2.3-71-gd317 From 0f292f023ffcc67ec49d63dcb7fe388711cbb83a Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Wed, 11 Jul 2018 13:37:53 +0100 Subject: ALSA: usb-audio: Add support for Processing Units in UAC3 This patch adds support for the Processig Units defined in the UAC3 spec. The main difference with the previous specs is the lack of on/off switches in the controls for these units and the addiction of the new Multi Function Processing Unit. The current version of the UAC3 spec doesn't define any useful controls for the new Multi Function Processing Unit so no control will get created once this unit is parsed. Signed-off-by: Jorge Sanjuan Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v3.h | 15 +++++++++++++ include/uapi/linux/usb/audio.h | 49 ++++++++++++++++++++++++++++++++-------- sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/include/linux/usb/audio-v3.h b/include/linux/usb/audio-v3.h index a710e28b5215..334bfa6dfb47 100644 --- a/include/linux/usb/audio-v3.h +++ b/include/linux/usb/audio-v3.h @@ -387,6 +387,12 @@ struct uac3_interrupt_data_msg { #define UAC3_CONNECTORS 0x0f #define UAC3_POWER_DOMAIN 0x10 +/* A.20 PROCESSING UNIT PROCESS TYPES */ +#define UAC3_PROCESS_UNDEFINED 0x00 +#define UAC3_PROCESS_UP_DOWNMIX 0x01 +#define UAC3_PROCESS_STEREO_EXTENDER 0x02 +#define UAC3_PROCESS_MULTI_FUNCTION 0x03 + /* A.22 AUDIO CLASS-SPECIFIC REQUEST CODES */ /* see audio-v2.h for the rest, which is identical to v2 */ #define UAC3_CS_REQ_INTEN 0x04 @@ -406,6 +412,15 @@ struct uac3_interrupt_data_msg { #define UAC3_TE_OVERFLOW 0x04 #define UAC3_TE_LATENCY 0x05 +/* A.23.10 PROCESSING UNITS CONTROL SELECTROS */ + +/* Up/Down Mixer */ +#define UAC3_UD_MODE_SELECT 0x01 + +/* Stereo Extender */ +#define UAC3_EXT_WIDTH_CONTROL 0x01 + + /* BADD predefined Unit/Terminal values */ #define UAC3_BADD_IT_ID1 1 /* Input Terminal ID1: bTerminalID = 1 */ #define UAC3_BADD_FU_ID2 2 /* Feature Unit ID2: bUnitID = 2 */ diff --git a/include/uapi/linux/usb/audio.h b/include/uapi/linux/usb/audio.h index 74e520fb944f..ddc5396800aa 100644 --- a/include/uapi/linux/usb/audio.h +++ b/include/uapi/linux/usb/audio.h @@ -390,33 +390,64 @@ static inline __u8 uac_processing_unit_iChannelNames(struct uac_processing_unit_ static inline __u8 uac_processing_unit_bControlSize(struct uac_processing_unit_descriptor *desc, int protocol) { - return (protocol == UAC_VERSION_1) ? - desc->baSourceID[desc->bNrInPins + 4] : - 2; /* in UAC2, this value is constant */ + switch (protocol) { + case UAC_VERSION_1: + return desc->baSourceID[desc->bNrInPins + 4]; + case UAC_VERSION_2: + return 2; /* in UAC2, this value is constant */ + case UAC_VERSION_3: + return 4; /* in UAC3, this value is constant */ + default: + return 1; + } } static inline __u8 *uac_processing_unit_bmControls(struct uac_processing_unit_descriptor *desc, int protocol) { - return (protocol == UAC_VERSION_1) ? - &desc->baSourceID[desc->bNrInPins + 5] : - &desc->baSourceID[desc->bNrInPins + 6]; + switch (protocol) { + case UAC_VERSION_1: + return &desc->baSourceID[desc->bNrInPins + 5]; + case UAC_VERSION_2: + return &desc->baSourceID[desc->bNrInPins + 6]; + case UAC_VERSION_3: + return &desc->baSourceID[desc->bNrInPins + 2]; + default: + return NULL; + } } static inline __u8 uac_processing_unit_iProcessing(struct uac_processing_unit_descriptor *desc, int protocol) { __u8 control_size = uac_processing_unit_bControlSize(desc, protocol); - return *(uac_processing_unit_bmControls(desc, protocol) - + control_size); + + switch (protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + return *(uac_processing_unit_bmControls(desc, protocol) + + control_size); + case UAC_VERSION_3: + return 0; /* UAC3 does not have this field */ + } } static inline __u8 *uac_processing_unit_specific(struct uac_processing_unit_descriptor *desc, int protocol) { __u8 control_size = uac_processing_unit_bControlSize(desc, protocol); - return uac_processing_unit_bmControls(desc, protocol) + + switch (protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + return uac_processing_unit_bmControls(desc, protocol) + control_size + 1; + case UAC_VERSION_3: + return uac_processing_unit_bmControls(desc, protocol) + + control_size; + } } /* 4.5.2 Class-Specific AS Interface Descriptor */ diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index bfb3484096a6..39fde49e8749 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -953,6 +953,23 @@ static int check_input_term(struct mixer_build *state, int id, return 0; } + case UAC3_PROCESSING_UNIT: { + struct uac_processing_unit_descriptor *d = p1; + + if (!d->bNrInPins) + return -EINVAL; + + /* call recursively to retrieve the channel info */ + err = check_input_term(state, d->baSourceID[0], term); + if (err < 0) + return err; + + term->type = d->bDescriptorSubtype << 16; /* virtual type */ + term->id = id; + term->name = 0; /* TODO: UAC3 Class-specific strings */ + + return 0; + } default: return -ENODEV; } @@ -2180,6 +2197,11 @@ struct procunit_info { struct procunit_value_info *values; }; +static struct procunit_value_info undefined_proc_info[] = { + { 0x00, "Control Undefined", 0 }, + { 0 } +}; + static struct procunit_value_info updown_proc_info[] = { { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN }, { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, @@ -2228,6 +2250,23 @@ static struct procunit_info procunits[] = { { UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info }, { 0 }, }; + +static struct procunit_value_info uac3_updown_proc_info[] = { + { UAC3_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, + { 0 } +}; +static struct procunit_value_info uac3_stereo_ext_proc_info[] = { + { UAC3_EXT_WIDTH_CONTROL, "Width Control", USB_MIXER_U8 }, + { 0 } +}; + +static struct procunit_info uac3_procunits[] = { + { UAC3_PROCESS_UP_DOWNMIX, "Up Down", uac3_updown_proc_info }, + { UAC3_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", uac3_stereo_ext_proc_info }, + { UAC3_PROCESS_MULTI_FUNCTION, "Multi-Function", undefined_proc_info }, + { 0 }, +}; + /* * predefined data for extension units */ @@ -2388,8 +2427,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, static int parse_audio_processing_unit(struct mixer_build *state, int unitid, void *raw_desc) { - return build_audio_procunit(state, unitid, raw_desc, - procunits, "Processing Unit"); + switch (state->mixer->protocol) { + case UAC_VERSION_1: + case UAC_VERSION_2: + default: + return build_audio_procunit(state, unitid, raw_desc, + procunits, "Processing Unit"); + case UAC_VERSION_3: + return build_audio_procunit(state, unitid, raw_desc, + uac3_procunits, "Processing Unit"); + } } static int parse_audio_extension_unit(struct mixer_build *state, int unitid, -- cgit v1.2.3-71-gd317 From 4aaf448fa9754e2d5ee188d32327b24ffc15ca4d Mon Sep 17 00:00:00 2001 From: Jim Qu Date: Tue, 17 Jul 2018 16:20:50 +0800 Subject: vga_switcheroo: set audio client id according to bound GPU id On modern laptop, there are more and more platforms have two GPUs, and each of them maybe have audio codec for HDMP/DP output. For some dGPU which is no output, audio codec usually is disabled. In currect HDA audio driver, it will set all codec as VGA_SWITCHEROO_DIS, the audio which is binded to UMA will be suspended if user use debugfs to contorl power In HDA driver side, it is difficult to know which GPU the audio has binded to. So set the bound gpu pci dev to vga_switcheroo. if the audio client is not the third registration, audio id will set in vga_switcheroo enable function. if the audio client is the last registration when vga_switcheroo _ready() get true, we should get audio client id from bound GPU directly. Signed-off-by: Jim Qu Reviewed-by: Lukas Wunner Signed-off-by: Takashi Iwai --- drivers/gpu/vga/vga_switcheroo.c | 63 +++++++++++++++++++++++++++++++++------- include/linux/vga_switcheroo.h | 8 ++--- sound/pci/hda/hda_intel.c | 11 +++---- 3 files changed, 62 insertions(+), 20 deletions(-) (limited to 'include/linux') diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index fc4adf3d34e8..a96bf46bc483 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -103,9 +103,11 @@ * runtime pm. If true, writing ON and OFF to the vga_switcheroo debugfs * interface is a no-op so as not to interfere with runtime pm * @list: client list + * @vga_dev: pci device, indicate which GPU is bound to current audio client * * Registered client. A client can be either a GPU or an audio device on a GPU. - * For audio clients, the @fb_info and @active members are bogus. + * For audio clients, the @fb_info and @active members are bogus. For GPU + * clients, the @vga_dev is bogus. */ struct vga_switcheroo_client { struct pci_dev *pdev; @@ -116,6 +118,7 @@ struct vga_switcheroo_client { bool active; bool driver_power_control; struct list_head list; + struct pci_dev *vga_dev; }; /* @@ -161,9 +164,8 @@ struct vgasr_priv { }; #define ID_BIT_AUDIO 0x100 -#define client_is_audio(c) ((c)->id & ID_BIT_AUDIO) -#define client_is_vga(c) ((c)->id == VGA_SWITCHEROO_UNKNOWN_ID || \ - !client_is_audio(c)) +#define client_is_audio(c) ((c)->id & ID_BIT_AUDIO) +#define client_is_vga(c) (!client_is_audio(c)) #define client_id(c) ((c)->id & ~ID_BIT_AUDIO) static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); @@ -192,14 +194,29 @@ static void vga_switcheroo_enable(void) vgasr_priv.handler->init(); list_for_each_entry(client, &vgasr_priv.clients, list) { - if (client->id != VGA_SWITCHEROO_UNKNOWN_ID) + if (!client_is_vga(client) || + client_id(client) != VGA_SWITCHEROO_UNKNOWN_ID) continue; + ret = vgasr_priv.handler->get_client_id(client->pdev); if (ret < 0) return; client->id = ret; } + + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (!client_is_audio(client) || + client_id(client) != VGA_SWITCHEROO_UNKNOWN_ID) + continue; + + ret = vgasr_priv.handler->get_client_id(client->vga_dev); + if (ret < 0) + return; + + client->id = ret | ID_BIT_AUDIO; + } + vga_switcheroo_debugfs_init(&vgasr_priv); vgasr_priv.active = true; } @@ -272,7 +289,9 @@ EXPORT_SYMBOL(vga_switcheroo_handler_flags); static int register_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, - enum vga_switcheroo_client_id id, bool active, + enum vga_switcheroo_client_id id, + struct pci_dev *vga_dev, + bool active, bool driver_power_control) { struct vga_switcheroo_client *client; @@ -287,6 +306,7 @@ static int register_client(struct pci_dev *pdev, client->id = id; client->active = active; client->driver_power_control = driver_power_control; + client->vga_dev = vga_dev; mutex_lock(&vgasr_mutex); list_add_tail(&client->list, &vgasr_priv.clients); @@ -319,7 +339,7 @@ int vga_switcheroo_register_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { - return register_client(pdev, ops, VGA_SWITCHEROO_UNKNOWN_ID, + return register_client(pdev, ops, VGA_SWITCHEROO_UNKNOWN_ID, NULL, pdev == vga_default_device(), driver_power_control); } @@ -329,19 +349,40 @@ EXPORT_SYMBOL(vga_switcheroo_register_client); * vga_switcheroo_register_audio_client - register audio client * @pdev: client pci device * @ops: client callbacks - * @id: client identifier + * @vga_dev: pci device which is bound to current audio client * * Register audio client (audio device on a GPU). The client is assumed * to use runtime PM. Beforehand, vga_switcheroo_client_probe_defer() * shall be called to ensure that all prerequisites are met. * - * Return: 0 on success, -ENOMEM on memory allocation error. + * Return: 0 on success, -ENOMEM on memory allocation error, -EINVAL on getting + * client id error. */ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, - enum vga_switcheroo_client_id id) + struct pci_dev *vga_dev) { - return register_client(pdev, ops, id | ID_BIT_AUDIO, false, true); + enum vga_switcheroo_client_id id = VGA_SWITCHEROO_UNKNOWN_ID; + + /* + * if vga_switcheroo has enabled, that mean two GPU clients and also + * handler are registered. Get audio client id from bound GPU client + * id directly, otherwise, set it as VGA_SWITCHEROO_UNKNOWN_ID, + * it will set to correct id in later when vga_switcheroo_enable() + * is called. + */ + mutex_lock(&vgasr_mutex); + if (vgasr_priv.active) { + id = vgasr_priv.handler->get_client_id(vga_dev); + if (id < 0) { + mutex_unlock(&vgasr_mutex); + return -EINVAL; + } + } + mutex_unlock(&vgasr_mutex); + + return register_client(pdev, ops, id | ID_BIT_AUDIO, vga_dev, + false, true); } EXPORT_SYMBOL(vga_switcheroo_register_audio_client); diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 77f0f0af3a71..a34539b7f750 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -84,8 +84,8 @@ enum vga_switcheroo_state { * Client identifier. Audio clients use the same identifier & 0x100. */ enum vga_switcheroo_client_id { - VGA_SWITCHEROO_UNKNOWN_ID = -1, - VGA_SWITCHEROO_IGD, + VGA_SWITCHEROO_UNKNOWN_ID = 0x1000, + VGA_SWITCHEROO_IGD = 0, VGA_SWITCHEROO_DIS, VGA_SWITCHEROO_MAX_CLIENTS, }; @@ -151,7 +151,7 @@ int vga_switcheroo_register_client(struct pci_dev *dev, bool driver_power_control); int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, - enum vga_switcheroo_client_id id); + struct pci_dev *vga_dev); void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info); @@ -180,7 +180,7 @@ static inline int vga_switcheroo_register_handler(const struct vga_switcheroo_ha enum vga_switcheroo_handler_flags_t handler_flags) { return 0; } static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, - enum vga_switcheroo_client_id id) { return 0; } + struct pci_dev *vga_dev) { return 0; } static inline void vga_switcheroo_unregister_handler(void) {} static inline enum vga_switcheroo_handler_flags_t vga_switcheroo_handler_flags(void) { return 0; } static inline int vga_switcheroo_lock_ddc(struct pci_dev *pdev) { return -ENODEV; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 1ae1850b3bfd..1967a5537d68 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1319,15 +1319,16 @@ static const struct vga_switcheroo_client_ops azx_vs_ops = { static int register_vga_switcheroo(struct azx *chip) { struct hda_intel *hda = container_of(chip, struct hda_intel, chip); + struct pci_dev *p; int err; if (!hda->use_vga_switcheroo) return 0; - /* FIXME: currently only handling DIS controller - * is there any machine with two switchable HDMI audio controllers? - */ - err = vga_switcheroo_register_audio_client(chip->pci, &azx_vs_ops, - VGA_SWITCHEROO_DIS); + + p = get_bound_vga(chip->pci); + err = vga_switcheroo_register_audio_client(chip->pci, &azx_vs_ops, p); + pci_dev_put(p); + if (err < 0) return err; hda->vga_switcheroo_registered = 1; -- cgit v1.2.3-71-gd317 From 11785ef53228d23ec386f5fe4a34601536f0c891 Mon Sep 17 00:00:00 2001 From: Jorge Sanjuan Date: Tue, 31 Jul 2018 13:28:42 +0100 Subject: ALSA: usb-audio: Initial Power Domain support Thee USB Audio Class 3 (UAC3) introduces Power Domains as a new feature to let a host turn individual parts of an audio function to different power states via USB requests. This lets the device get to know a bit amore about what the host is up to in order to optimize power consumption efficiently. The Power Domains are optional for UAC3 configuration but all UAC3 devices shall include at least one BADD configuration where the support for Power Domains is compulsory. This patch adds a set of features/helpers to parse these power domains and change their status. Signed-off-by: Jorge Sanjuan Signed-off-by: Takashi Iwai --- include/linux/usb/audio-v3.h | 4 ++ sound/usb/Makefile | 1 + sound/usb/power.c | 104 +++++++++++++++++++++++++++++++++++++++++++ sound/usb/power.h | 19 ++++++++ 4 files changed, 128 insertions(+) create mode 100644 sound/usb/power.c (limited to 'include/linux') diff --git a/include/linux/usb/audio-v3.h b/include/linux/usb/audio-v3.h index 334bfa6dfb47..6b708434b7f9 100644 --- a/include/linux/usb/audio-v3.h +++ b/include/linux/usb/audio-v3.h @@ -447,4 +447,8 @@ struct uac3_interrupt_data_msg { /* BADD sample rate is always fixed to 48kHz */ #define UAC3_BADD_SAMPLING_RATE 48000 +/* BADD power domains recovery times in 50us increments */ +#define UAC3_BADD_PD_RECOVER_D1D0 0x0258 /* 30ms */ +#define UAC3_BADD_PD_RECOVER_D2D0 0x1770 /* 300ms */ + #endif /* __LINUX_USB_AUDIO_V3_H */ diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 05440e2df8d9..d330f74c90e6 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -13,6 +13,7 @@ snd-usb-audio-objs := card.o \ mixer_scarlett.o \ mixer_us16x08.o \ pcm.o \ + power.o \ proc.o \ quirks.o \ stream.o diff --git a/sound/usb/power.c b/sound/usb/power.c new file mode 100644 index 000000000000..bd303a1ba1b7 --- /dev/null +++ b/sound/usb/power.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UAC3 Power Domain state management functions + */ + +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "helper.h" +#include "power.h" + +struct snd_usb_power_domain * +snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, + unsigned char id) +{ + struct snd_usb_power_domain *pd; + void *p; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return NULL; + + p = NULL; + while ((p = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + p, UAC3_POWER_DOMAIN)) != NULL) { + struct uac3_power_domain_descriptor *pd_desc = p; + int i; + + for (i = 0; i < pd_desc->bNrEntities; i++) { + if (pd_desc->baEntityID[i] == id) { + pd->pd_id = pd_desc->bPowerDomainID; + pd->pd_d1d0_rec = + le16_to_cpu(pd_desc->waRecoveryTime1); + pd->pd_d2d0_rec = + le16_to_cpu(pd_desc->waRecoveryTime2); + return pd; + } + } + } + + kfree(pd); + return NULL; +} + +int snd_usb_power_domain_set(struct snd_usb_audio *chip, + struct snd_usb_power_domain *pd, + unsigned char state) +{ + struct usb_device *dev = chip->dev; + unsigned char current_state; + int err, idx; + + idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8); + + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx, + ¤t_state, sizeof(current_state)); + if (err < 0) { + dev_err(&dev->dev, "Can't get UAC3 power state for id %d\n", + pd->pd_id); + return err; + } + + if (current_state == state) { + dev_dbg(&dev->dev, "UAC3 power domain id %d already in state %d\n", + pd->pd_id, state); + return 0; + } + + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, + UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx, + &state, sizeof(state)); + if (err < 0) { + dev_err(&dev->dev, "Can't set UAC3 power state to %d for id %d\n", + state, pd->pd_id); + return err; + } + + if (state == UAC3_PD_STATE_D0) { + switch (current_state) { + case UAC3_PD_STATE_D2: + udelay(pd->pd_d2d0_rec * 50); + break; + case UAC3_PD_STATE_D1: + udelay(pd->pd_d1d0_rec * 50); + break; + default: + return -EINVAL; + } + } + + dev_dbg(&dev->dev, "UAC3 power domain id %d change to state %d\n", + pd->pd_id, state); + + return 0; +} diff --git a/sound/usb/power.h b/sound/usb/power.h index b2e25f60c5a2..6004231a7c75 100644 --- a/sound/usb/power.h +++ b/sound/usb/power.h @@ -2,6 +2,25 @@ #ifndef __USBAUDIO_POWER_H #define __USBAUDIO_POWER_H +struct snd_usb_power_domain { + int pd_id; /* UAC3 Power Domain ID */ + int pd_d1d0_rec; /* D1 to D0 recovery time */ + int pd_d2d0_rec; /* D2 to D0 recovery time */ +}; + +enum { + UAC3_PD_STATE_D0, + UAC3_PD_STATE_D1, + UAC3_PD_STATE_D2, +}; + +int snd_usb_power_domain_set(struct snd_usb_audio *chip, + struct snd_usb_power_domain *pd, + unsigned char state); +struct snd_usb_power_domain * +snd_usb_find_power_domain(struct usb_host_interface *ctrl_iface, + unsigned char id); + #ifdef CONFIG_PM int snd_usb_autoresume(struct snd_usb_audio *chip); void snd_usb_autosuspend(struct snd_usb_audio *chip); -- cgit v1.2.3-71-gd317