2015-02-16 21:03:09

by Peter Rosin

[permalink] [raw]
Subject: [PATCH 0/2] ASoC: pcm512x: Add knobs to allow and control overclocking

From: Peter Rosin <[email protected]>

Hi!

I wasn't sure if I should add Documentation/* for these sysfs knobs
or not? A lot of knobs do not seem to have docs (no specific example,
just a gut feeling). And I'm not sure how I should name the doc-file
since the pcm512x driver handles devices connected with both i2c and
spi. And what KernelVersion should I enter? That depends... So, this
isn't perfect, suggestions welcome.

The first patch is a preparatory patch that makes 2/2 slightly simpler.
I can fold them together if that's desired. Or I could split 2/2 up in
three logical patches, one for each of the independent PLL/DSP/DAC
knobs.

Another "feature" that one might want, is that attempts to change the
overclocking should fail with EBUSY when the device is already in use.
But I haven't invested enough time to find out how I should determine
when to fail, so for this version the driver accepts the change but
doesn't enforce the new limit until the clock rates are recalculated.

Cheers,
Peter

Peter Rosin (2):
ASoC: pcm512x: Rearrange to not repeat dacsrc_rate / dac_div
ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

Documentation/ABI/testing/sysfs-i2c-pcm512x | 35 +++++
sound/soc/codecs/pcm512x.c | 189 ++++++++++++++++++++++++---
2 files changed, 205 insertions(+), 19 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-i2c-pcm512x

--
1.7.10.4


2015-02-16 21:03:11

by Peter Rosin

[permalink] [raw]
Subject: [PATCH 0/2] ASoC: pcm512x: Add knobs to allow and control overclocking

From: Peter Rosin <[email protected]>

Hi!

I wasn't sure if I should add Documentation/* for these sysfs knobs
or not? A lot of knobs do not have docs... And I'm not sure how I
should name the doc-file since the pcm512x driver handles devices
connected with both i2c and spi. So, this isn't perfect, suggestions
welcome.

The first patch is a preparatory patch that makes 2/2 slightly simpler.
I can fold them together if that's desired. Or I could split 2/2 up in
three logical pieces, one for each of the independent PLL/DSP/DAC
knob. Whatever...

Cheers,
Peter

Peter Rosin (2):
ASoC: pcm512x: Rearrange to not repeat dacsrc_rate / dac_div
ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

Documentation/ABI/testing/sysfs-i2c-pcm512x | 35 +++++
sound/soc/codecs/pcm512x.c | 189 ++++++++++++++++++++++++---
2 files changed, 205 insertions(+), 19 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-i2c-pcm512x

--
1.7.10.4

2015-02-16 21:03:12

by Peter Rosin

[permalink] [raw]
Subject: [PATCH 1/2] ASoC: pcm512x: Rearrange to not repeat dacsrc_rate / dac_div

From: Peter Rosin <[email protected]>

Signed-off-by: Peter Rosin <[email protected]>
---
sound/soc/codecs/pcm512x.c | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 884784fb1566..f13ff7578c78 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -863,28 +863,29 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
dacsrc_rate = sck_rate;
}

+ osr_div = DIV_ROUND_CLOSEST(dac_rate, osr_rate);
+ if (osr_div > 128) {
+ dev_err(dev, "Failed to find OSR divider\n");
+ return -EINVAL;
+ }
+
dac_div = DIV_ROUND_CLOSEST(dacsrc_rate, dac_rate);
if (dac_div > 128) {
dev_err(dev, "Failed to find DAC divider\n");
return -EINVAL;
}
+ dac_rate = dacsrc_rate / dac_div;

- ncp_div = DIV_ROUND_CLOSEST(dacsrc_rate / dac_div, 1536000);
- if (ncp_div > 128 || dacsrc_rate / dac_div / ncp_div > 2048000) {
+ ncp_div = DIV_ROUND_CLOSEST(dac_rate, 1536000);
+ if (ncp_div > 128 || dac_rate / ncp_div > 2048000) {
/* run NCP no faster than 2048000 Hz, but why? */
- ncp_div = DIV_ROUND_UP(dacsrc_rate / dac_div, 2048000);
+ ncp_div = DIV_ROUND_UP(dac_rate, 2048000);
if (ncp_div > 128) {
dev_err(dev, "Failed to find NCP divider\n");
return -EINVAL;
}
}

- osr_div = DIV_ROUND_CLOSEST(dac_rate, osr_rate);
- if (osr_div > 128) {
- dev_err(dev, "Failed to find OSR divider\n");
- return -EINVAL;
- }
-
idac = mck_rate / (dsp_div * sample_rate);

ret = regmap_write(pcm512x->regmap, PCM512x_DSP_CLKDIV, dsp_div - 1);
--
1.7.10.4

2015-02-16 21:03:19

by Peter Rosin

[permalink] [raw]
Subject: [PATCH 2/2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

From: Peter Rosin <[email protected]>

When using non-standard rates, a relatively small amount of overclocking
can make a big difference to a number of cases.

- Not all rates are possible to achieve with the PLL, due to divider
restrictions.

- The higher oversampling rates that can be used by the DAC, the
simpler the analog output filters get (mirror frequencies move up,
away from the desired spectrum).

- The more work the DSP can perform per sample, the better.

For standard rates, there is little to gain as everything is
designed just right, and the needed overclocking to make a
real difference would be significant.

Signed-off-by: Peter Rosin <[email protected]>
---
Documentation/ABI/testing/sysfs-i2c-pcm512x | 35 ++++++
sound/soc/codecs/pcm512x.c | 172 +++++++++++++++++++++++++--
2 files changed, 196 insertions(+), 11 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-i2c-pcm512x

diff --git a/Documentation/ABI/testing/sysfs-i2c-pcm512x b/Documentation/ABI/testing/sysfs-i2c-pcm512x
new file mode 100644
index 000000000000..596cd97788db
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-i2c-pcm512x
@@ -0,0 +1,35 @@
+What: /sys/bus/i2c/devices/<busnum>-<devaddr>/overclock_pll
+Date: February 2015
+Contact: Peter Rosin <[email protected]>
+Description: When the codec acts as clock master, tell the pcm512x
+ to allow overclocking the PLL (as a percentage). Zero
+ disables PLL overclocking.
+
+ Reading: returns the current PLL overclocking setting.
+
+ Writing: set a new PLL overclocking setting.
+ Accepted values: 0..20.
+
+What: /sys/bus/i2c/devices/<busnum>-<devaddr>/overclock_dac
+Date: February 2015
+Contact: Peter Rosin <[email protected]>
+Description: When the codec acts as clock master, tell the pcm512x
+ to allow overclocking the DAC (as a percentage). Zero
+ disables DAC overclocking.
+
+ Reading: returns the current DAC overclocking setting.
+
+ Writing: set a new DAC overclocking setting.
+ Accepted values: 0..40.
+
+What: /sys/bus/i2c/devices/<busnum>-<devaddr>/overclock_dsp
+Date: February 2015
+Contact: Peter Rosin <[email protected]>
+Description: When the codec acts as clock master, tell the pcm512x
+ to allow overclocking the DSP (as a percentage). Zero
+ disables DSP overclocking.
+
+ Reading: returns the current DSP overclocking setting.
+
+ Writing: set a new DSP overclocking setting.
+ Accepted values: 0..40.
diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index f13ff7578c78..f4d3dc390aed 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -54,6 +54,9 @@ struct pcm512x_priv {
int pll_d;
int pll_p;
unsigned long real_pll;
+ unsigned long overclock_pll;
+ unsigned long overclock_dac;
+ unsigned long overclock_dsp;
};

/*
@@ -346,6 +349,132 @@ static const struct snd_soc_dapm_route pcm512x_dapm_routes[] = {
{ "OUTR", NULL, "DACR" },
};

+static ssize_t pcm512x_overclock_pll(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lu\n", pcm512x->overclock_pll);
+}
+
+static ssize_t pcm512x_overclock_pll_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+ unsigned long value;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &value);
+ if (ret)
+ return ret;
+ if (value > 20)
+ return -EINVAL;
+ pcm512x->overclock_pll = value;
+
+ return count;
+}
+
+static DEVICE_ATTR(overclock_pll, 0644,
+ pcm512x_overclock_pll, pcm512x_overclock_pll_set);
+
+static ssize_t pcm512x_overclock_dsp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lu\n", pcm512x->overclock_dsp);
+}
+
+static ssize_t pcm512x_overclock_dsp_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+ unsigned long value;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &value);
+ if (ret)
+ return ret;
+ if (value > 40)
+ return -EINVAL;
+ pcm512x->overclock_dsp = value;
+
+ return count;
+}
+
+static DEVICE_ATTR(overclock_dsp, 0644,
+ pcm512x_overclock_dsp, pcm512x_overclock_dsp_set);
+
+static ssize_t pcm512x_overclock_dac(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%lu\n", pcm512x->overclock_dac);
+}
+
+static ssize_t pcm512x_overclock_dac_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+ unsigned long value;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &value);
+ if (ret)
+ return ret;
+ if (value > 40)
+ return -EINVAL;
+ pcm512x->overclock_dac = value;
+
+ return count;
+}
+
+static DEVICE_ATTR(overclock_dac, 0644,
+ pcm512x_overclock_dac, pcm512x_overclock_dac_set);
+
+static unsigned long pcm512x_pll_max(struct pcm512x_priv *pcm512x)
+{
+ return 25000000 + 25000000 * pcm512x->overclock_pll / 100;
+}
+
+static unsigned long pcm512x_dsp_max(struct pcm512x_priv *pcm512x)
+{
+ return 50000000 + 50000000 * pcm512x->overclock_dsp / 100;
+}
+
+static unsigned long pcm512x_dac_max(struct pcm512x_priv *pcm512x,
+ unsigned long rate)
+{
+ return rate + rate * pcm512x->overclock_dac / 100;
+}
+
+static unsigned long pcm512x_sck_max(struct pcm512x_priv *pcm512x)
+{
+ if (!pcm512x->pll_out)
+ return 25000000;
+ return pcm512x_pll_max(pcm512x);
+}
+
+static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x,
+ unsigned long dac_rate)
+{
+ /*
+ * If the DAC is not actually overclocked, use the good old
+ * NCP target rate...
+ */
+ if (dac_rate <= 6144000)
+ return 1536000;
+ /*
+ * ...but if the DAC is in fact overclocked, bump the NCP target
+ * rate to get the recommended dividers even when overclocking.
+ */
+ return pcm512x_dac_max(pcm512x, 1536000);
+}
+
static const u32 pcm512x_dai_rates[] = {
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
88200, 96000, 176400, 192000, 384000,
@@ -359,6 +488,7 @@ static const struct snd_pcm_hw_constraint_list constraints_slave = {
static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
+ struct pcm512x_priv *pcm512x = rule->private;
struct snd_interval ranges[2];
int frame_size;

@@ -377,7 +507,7 @@ static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params,
*/
memset(ranges, 0, sizeof(ranges));
ranges[0].min = 8000;
- ranges[0].max = 25000000 / frame_size / 2;
+ ranges[0].max = pcm512x_sck_max(pcm512x) / frame_size / 2;
ranges[1].min = DIV_ROUND_UP(16000000, frame_size);
ranges[1].max = 384000;
break;
@@ -408,7 +538,7 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
pcm512x_hw_rule_rate,
- NULL,
+ pcm512x,
SNDRV_PCM_HW_PARAM_FRAME_BITS,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);

@@ -517,6 +647,8 @@ static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai,
unsigned long bclk_rate)
{
struct device *dev = dai->dev;
+ struct snd_soc_codec *codec = dai->codec;
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
unsigned long sck_rate;
int pow2;

@@ -527,9 +659,10 @@ static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai,
* as many factors of 2 as possible, as that makes it easier
* to find a fast DAC rate
*/
- pow2 = 1 << fls((25000000 - 16000000) / bclk_rate);
+ pow2 = 1 << fls((pcm512x_pll_max(pcm512x) - 16000000) / bclk_rate);
for (; pow2; pow2 >>= 1) {
- sck_rate = rounddown(25000000, bclk_rate * pow2);
+ sck_rate = rounddown(pcm512x_pll_max(pcm512x),
+ bclk_rate * pow2);
if (sck_rate >= 16000000)
break;
}
@@ -678,7 +811,7 @@ static unsigned long pcm512x_pllin_dac_rate(struct snd_soc_dai *dai,
return 0; /* futile, quit early */

/* run DAC no faster than 6144000 Hz */
- for (dac_rate = rounddown(6144000, osr_rate);
+ for (dac_rate = rounddown(pcm512x_dac_max(pcm512x, 6144000), osr_rate);
dac_rate;
dac_rate -= osr_rate) {

@@ -805,7 +938,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
osr_rate = 16 * sample_rate;

/* run DSP no faster than 50 MHz */
- dsp_div = mck_rate > 50000000 ? 2 : 1;
+ dsp_div = mck_rate > pcm512x_dsp_max(pcm512x) ? 2 : 1;

dac_rate = pcm512x_pllin_dac_rate(dai, osr_rate, pllin_rate);
if (dac_rate) {
@@ -836,7 +969,8 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
dacsrc_rate = pllin_rate;
} else {
/* run DAC no faster than 6144000 Hz */
- unsigned long dac_mul = 6144000 / osr_rate;
+ unsigned long dac_mul = pcm512x_dac_max(pcm512x, 6144000)
+ / osr_rate;
unsigned long sck_mul = sck_rate / osr_rate;

for (; dac_mul; dac_mul--) {
@@ -876,7 +1010,8 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
}
dac_rate = dacsrc_rate / dac_div;

- ncp_div = DIV_ROUND_CLOSEST(dac_rate, 1536000);
+ ncp_div = DIV_ROUND_CLOSEST(dac_rate,
+ pcm512x_ncp_target(pcm512x, dac_rate));
if (ncp_div > 128 || dac_rate / ncp_div > 2048000) {
/* run NCP no faster than 2048000 Hz, but why? */
ncp_div = DIV_ROUND_UP(dac_rate, 2048000);
@@ -938,11 +1073,11 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
return ret;
}

- if (sample_rate <= 48000)
+ if (sample_rate <= pcm512x_dac_max(pcm512x, 48000))
fssp = PCM512x_FSSP_48KHZ;
- else if (sample_rate <= 96000)
+ else if (sample_rate <= pcm512x_dac_max(pcm512x, 96000))
fssp = PCM512x_FSSP_96KHZ;
- else if (sample_rate <= 192000)
+ else if (sample_rate <= pcm512x_dac_max(pcm512x, 192000))
fssp = PCM512x_FSSP_192KHZ;
else
fssp = PCM512x_FSSP_384KHZ;
@@ -1389,6 +1524,18 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
goto err_pm;
}

+ ret = device_create_file(dev, &dev_attr_overclock_pll);
+ if (ret < 0)
+ dev_warn(dev, "Failed to add overclock_pll sysfs: %d\n", ret);
+
+ ret = device_create_file(dev, &dev_attr_overclock_dsp);
+ if (ret < 0)
+ dev_warn(dev, "Failed to add overclock_dsp sysfs: %d\n", ret);
+
+ ret = device_create_file(dev, &dev_attr_overclock_dac);
+ if (ret < 0)
+ dev_warn(dev, "Failed to add overclock_dac sysfs: %d\n", ret);
+
return 0;

err_pm:
@@ -1407,6 +1554,9 @@ void pcm512x_remove(struct device *dev)
{
struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);

+ device_remove_file(dev, &dev_attr_overclock_pll);
+ device_remove_file(dev, &dev_attr_overclock_dsp);
+ device_remove_file(dev, &dev_attr_overclock_dac);
snd_soc_unregister_codec(dev);
pm_runtime_disable(dev);
if (!IS_ERR(pcm512x->sclk))
--
1.7.10.4

2015-02-17 01:56:23

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 0/2] ASoC: pcm512x: Add knobs to allow and control overclocking

On Mon, Feb 16, 2015 at 10:02:46PM +0100, Peter Rosin wrote:

> I wasn't sure if I should add Documentation/* for these sysfs knobs
> or not? A lot of knobs do not have docs... And I'm not sure how I
> should name the doc-file since the pcm512x driver handles devices
> connected with both i2c and spi. So, this isn't perfect, suggestions
> welcome.

It's supposed to be mandatory to have documentation but not well
enforced.


Attachments:
(No filename) (426.00 B)
signature.asc (473.00 B)
Digital signature
Download all attachments

2015-02-21 09:34:45

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

On Mon, Feb 16, 2015 at 10:02:48PM +0100, Peter Rosin wrote:
> From: Peter Rosin <[email protected]>
>
> When using non-standard rates, a relatively small amount of overclocking
> can make a big difference to a number of cases.

This is all basically fine but I'm wondering why this is being
configured via sysfs and not via ALSA controls? It's going to be more
fiddly for people to have to work with both control methods when they
need to configure these things.


Attachments:
(No filename) (464.00 B)
signature.asc (473.00 B)
Digital signature
Download all attachments

2015-02-21 09:35:15

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: pcm512x: Rearrange to not repeat dacsrc_rate / dac_div

On Mon, Feb 16, 2015 at 10:02:47PM +0100, Peter Rosin wrote:
> From: Peter Rosin <[email protected]>

Applied, thanks.


Attachments:
(No filename) (117.00 B)
signature.asc (473.00 B)
Digital signature
Download all attachments

2015-02-21 09:35:52

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 0/2] ASoC: pcm512x: Add knobs to allow and control overclocking

On Mon, Feb 16, 2015 at 10:02:45PM +0100, Peter Rosin wrote:

> Another "feature" that one might want, is that attempts to change the
> overclocking should fail with EBUSY when the device is already in use.
> But I haven't invested enough time to find out how I should determine
> when to fail, so for this version the driver accepts the change but
> doesn't enforce the new limit until the clock rates are recalculated.

You should be able to to check for the bias level being set to ON.


Attachments:
(No filename) (489.00 B)
signature.asc (473.00 B)
Digital signature
Download all attachments

2015-02-22 00:24:31

by Peter Rosin

[permalink] [raw]
Subject: RE: [PATCH 2/2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

Mark Brown wrote:
> On Mon, Feb 16, 2015 at 10:02:48PM +0100, Peter Rosin wrote:
> > From: Peter Rosin <[email protected]>
> >
> > When using non-standard rates, a relatively small amount of
> > overclocking can make a big difference to a number of cases.
>
> This is all basically fine but I'm wondering why this is being configured via
> sysfs and not via ALSA controls? It's going to be more fiddly for people to
> have to work with both control methods when they need to configure these
> things.

Originally, I had the limits in .config. Then Lars-Peter suggested
sysfs (on irc) and perhaps some way to disable overclocking. ALSA
controls were never on the table, but now that you mention it, it sounds
about right. So, I'm fine with having it as ALSA-controls...

*time passes*

...but I'm not sure everybody agrees that overclocking games should
be allowed by any and all users?

If you still want me to convert to ALSA controls, what control do you
suggest? SOC_SINGLE_EXT? Or should I use an enumeration, because
mixers tend to present volume controls as a percentage of max, which
will be confusing: You are now at "volume" 75% (of max 40), when the
value is 30. Eeek. But enumerations from 0% to 40% sounds tedious.

And how would you suggest that I name the controls?
"Max Overclock DAC", "Max Overclock DSP" and "Max Overclock PLL"?

BTW, the only troubles I've had with overclocking "too much" is that it
has stopped working. I have not managed to fry any chip. But that is no
guarantee, of course.

Cheers,
Peter

2015-02-23 14:32:44

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

On Sun, Feb 22, 2015 at 12:24:12AM +0000, Peter Rosin wrote:

> ...but I'm not sure everybody agrees that overclocking games should
> be allowed by any and all users?

I don't see why not, ASoC controls are already way beyond end user a lot
of the time.

> If you still want me to convert to ALSA controls, what control do you
> suggest? SOC_SINGLE_EXT? Or should I use an enumeration, because
> mixers tend to present volume controls as a percentage of max, which
> will be confusing: You are now at "volume" 75% (of max 40), when the
> value is 30. Eeek. But enumerations from 0% to 40% sounds tedious.

I'd just use a single value, it's a UI problem with the mixer and if the
control isn't called "Volume" that shouldn cause the UI to not present
it as a volume. He said hopefully.

Or possibly a binary control I guess which would have the nice side
effect of hiding from most non-specialist UIs.

> And how would you suggest that I name the controls?
> "Max Overclock DAC", "Max Overclock DSP" and "Max Overclock PLL"?

Those sound reasonable.

> BTW, the only troubles I've had with overclocking "too much" is that it
> has stopped working. I have not managed to fry any chip. But that is no
> guarantee, of course.

It's vanishingly unlikely that you'll cause physical damage with this
sort of thing, you're much more likely to hit performance and filter
problems than anything else.


Attachments:
(No filename) (1.36 kB)
signature.asc (473.00 B)
Digital signature
Download all attachments

2015-02-23 20:04:17

by Peter Rosin

[permalink] [raw]
Subject: Re: [PATCH 2/2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

From: Peter Rosin <[email protected]>

On 2015-02-23 15:31, Mark Brown wrote:
> On Sun, Feb 22, 2015 at 12:24:12AM +0000, Peter Rosin wrote:
>
>> ...but I'm not sure everybody agrees that overclocking games should
>> be allowed by any and all users?
>
> I don't see why not, ASoC controls are already way beyond end user a lot
> of the time.

*snip*

Ok, so going to ALSA-control route, changes since v1:

- Uses ALSA-controls instead of sysfs knobs.
- Returns -EBUSY if trying to change the values while active.

Cheers,
Peter

Peter Rosin (1):
ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

sound/soc/codecs/pcm512x.c | 161 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 150 insertions(+), 11 deletions(-)

--
1.7.10.4

2015-02-23 20:04:25

by Peter Rosin

[permalink] [raw]
Subject: [PATCH v2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

From: Peter Rosin <[email protected]>

When using non-standard rates, a relatively small amount of overclocking
can make a big difference to a number of cases.

- Not all rates are possible to achieve with the PLL, due to divider
restrictions.

- The higher oversampling rates that can be used by the DAC, the
simpler the analog output filters get (mirror frequencies move up,
away from the desired spectrum).

- The more work the DSP can perform per sample, the better.

For standard rates, there is little to gain as everything is
designed just right, and the needed overclocking to make a
real difference would be significant.

Signed-off-by: Peter Rosin <[email protected]>
---
sound/soc/codecs/pcm512x.c | 161 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 150 insertions(+), 11 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index f11c76f1acfe..4b5f1fe9be97 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -54,6 +54,9 @@ struct pcm512x_priv {
int pll_d;
int pll_p;
unsigned long real_pll;
+ unsigned long overclock_pll;
+ unsigned long overclock_dac;
+ unsigned long overclock_dsp;
};

/*
@@ -224,6 +227,90 @@ static bool pcm512x_volatile(struct device *dev, unsigned int reg)
}
}

+static int pcm512x_overclock_pll_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = pcm512x->overclock_pll;
+ return 0;
+}
+
+static int pcm512x_overclock_pll_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_OFF:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ pcm512x->overclock_pll = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int pcm512x_overclock_dsp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = pcm512x->overclock_dsp;
+ return 0;
+}
+
+static int pcm512x_overclock_dsp_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_OFF:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ pcm512x->overclock_dsp = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static int pcm512x_overclock_dac_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = pcm512x->overclock_dac;
+ return 0;
+}
+
+static int pcm512x_overclock_dac_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+ switch (codec->dapm.bias_level) {
+ case SND_SOC_BIAS_OFF:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ default:
+ return -EBUSY;
+ }
+
+ pcm512x->overclock_dac = ucontrol->value.integer.value[0];
+ return 0;
+}
+
static const DECLARE_TLV_DB_SCALE(digital_tlv, -10350, 50, 1);
static const DECLARE_TLV_DB_SCALE(analog_tlv, -600, 600, 0);
static const DECLARE_TLV_DB_SCALE(boost_tlv, 0, 80, 0);
@@ -328,6 +415,13 @@ SOC_ENUM("Volume Ramp Up Rate", pcm512x_vnuf),
SOC_ENUM("Volume Ramp Up Step", pcm512x_vnus),
SOC_ENUM("Volume Ramp Down Emergency Rate", pcm512x_vedf),
SOC_ENUM("Volume Ramp Down Emergency Step", pcm512x_veds),
+
+SOC_SINGLE_EXT("Max Overclock PLL", SND_SOC_NOPM, 0, 20, 0,
+ pcm512x_overclock_pll_get, pcm512x_overclock_pll_put),
+SOC_SINGLE_EXT("Max Overclock DSP", SND_SOC_NOPM, 0, 40, 0,
+ pcm512x_overclock_dsp_get, pcm512x_overclock_dsp_put),
+SOC_SINGLE_EXT("Max Overclock DAC", SND_SOC_NOPM, 0, 40, 0,
+ pcm512x_overclock_dac_get, pcm512x_overclock_dac_put),
};

static const struct snd_soc_dapm_widget pcm512x_dapm_widgets[] = {
@@ -346,6 +440,45 @@ static const struct snd_soc_dapm_route pcm512x_dapm_routes[] = {
{ "OUTR", NULL, "DACR" },
};

+static unsigned long pcm512x_pll_max(struct pcm512x_priv *pcm512x)
+{
+ return 25000000 + 25000000 * pcm512x->overclock_pll / 100;
+}
+
+static unsigned long pcm512x_dsp_max(struct pcm512x_priv *pcm512x)
+{
+ return 50000000 + 50000000 * pcm512x->overclock_dsp / 100;
+}
+
+static unsigned long pcm512x_dac_max(struct pcm512x_priv *pcm512x,
+ unsigned long rate)
+{
+ return rate + rate * pcm512x->overclock_dac / 100;
+}
+
+static unsigned long pcm512x_sck_max(struct pcm512x_priv *pcm512x)
+{
+ if (!pcm512x->pll_out)
+ return 25000000;
+ return pcm512x_pll_max(pcm512x);
+}
+
+static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x,
+ unsigned long dac_rate)
+{
+ /*
+ * If the DAC is not actually overclocked, use the good old
+ * NCP target rate...
+ */
+ if (dac_rate <= 6144000)
+ return 1536000;
+ /*
+ * ...but if the DAC is in fact overclocked, bump the NCP target
+ * rate to get the recommended dividers even when overclocking.
+ */
+ return pcm512x_dac_max(pcm512x, 1536000);
+}
+
static const u32 pcm512x_dai_rates[] = {
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
88200, 96000, 176400, 192000, 384000,
@@ -359,6 +492,7 @@ static const struct snd_pcm_hw_constraint_list constraints_slave = {
static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
+ struct pcm512x_priv *pcm512x = rule->private;
struct snd_interval ranges[2];
int frame_size;

@@ -377,7 +511,7 @@ static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params,
*/
memset(ranges, 0, sizeof(ranges));
ranges[0].min = 8000;
- ranges[0].max = 25000000 / frame_size / 2;
+ ranges[0].max = pcm512x_sck_max(pcm512x) / frame_size / 2;
ranges[1].min = DIV_ROUND_UP(16000000, frame_size);
ranges[1].max = 384000;
break;
@@ -408,7 +542,7 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
return snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
pcm512x_hw_rule_rate,
- NULL,
+ pcm512x,
SNDRV_PCM_HW_PARAM_FRAME_BITS,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);

@@ -517,6 +651,8 @@ static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai,
unsigned long bclk_rate)
{
struct device *dev = dai->dev;
+ struct snd_soc_codec *codec = dai->codec;
+ struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
unsigned long sck_rate;
int pow2;

@@ -527,9 +663,10 @@ static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai,
* as many factors of 2 as possible, as that makes it easier
* to find a fast DAC rate
*/
- pow2 = 1 << fls((25000000 - 16000000) / bclk_rate);
+ pow2 = 1 << fls((pcm512x_pll_max(pcm512x) - 16000000) / bclk_rate);
for (; pow2; pow2 >>= 1) {
- sck_rate = rounddown(25000000, bclk_rate * pow2);
+ sck_rate = rounddown(pcm512x_pll_max(pcm512x),
+ bclk_rate * pow2);
if (sck_rate >= 16000000)
break;
}
@@ -678,7 +815,7 @@ static unsigned long pcm512x_pllin_dac_rate(struct snd_soc_dai *dai,
return 0; /* futile, quit early */

/* run DAC no faster than 6144000 Hz */
- for (dac_rate = rounddown(6144000, osr_rate);
+ for (dac_rate = rounddown(pcm512x_dac_max(pcm512x, 6144000), osr_rate);
dac_rate;
dac_rate -= osr_rate) {

@@ -805,7 +942,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
osr_rate = 16 * sample_rate;

/* run DSP no faster than 50 MHz */
- dsp_div = mck_rate > 50000000 ? 2 : 1;
+ dsp_div = mck_rate > pcm512x_dsp_max(pcm512x) ? 2 : 1;

dac_rate = pcm512x_pllin_dac_rate(dai, osr_rate, pllin_rate);
if (dac_rate) {
@@ -836,7 +973,8 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
dacsrc_rate = pllin_rate;
} else {
/* run DAC no faster than 6144000 Hz */
- unsigned long dac_mul = 6144000 / osr_rate;
+ unsigned long dac_mul = pcm512x_dac_max(pcm512x, 6144000)
+ / osr_rate;
unsigned long sck_mul = sck_rate / osr_rate;

for (; dac_mul; dac_mul--) {
@@ -876,7 +1014,8 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
}
dac_rate = dacsrc_rate / dac_div;

- ncp_div = DIV_ROUND_CLOSEST(dac_rate, 1536000);
+ ncp_div = DIV_ROUND_CLOSEST(dac_rate,
+ pcm512x_ncp_target(pcm512x, dac_rate));
if (ncp_div > 128 || dac_rate / ncp_div > 2048000) {
/* run NCP no faster than 2048000 Hz, but why? */
ncp_div = DIV_ROUND_UP(dac_rate, 2048000);
@@ -938,11 +1077,11 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
return ret;
}

- if (sample_rate <= 48000)
+ if (sample_rate <= pcm512x_dac_max(pcm512x, 48000))
fssp = PCM512x_FSSP_48KHZ;
- else if (sample_rate <= 96000)
+ else if (sample_rate <= pcm512x_dac_max(pcm512x, 96000))
fssp = PCM512x_FSSP_96KHZ;
- else if (sample_rate <= 192000)
+ else if (sample_rate <= pcm512x_dac_max(pcm512x, 192000))
fssp = PCM512x_FSSP_192KHZ;
else
fssp = PCM512x_FSSP_384KHZ;
--
1.7.10.4

2015-02-24 14:15:39

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2] ASoC: pcm512x: Allow independently overclocking PLL, DAC and DSP

On Mon, Feb 23, 2015 at 09:03:33PM +0100, Peter Rosin wrote:
> From: Peter Rosin <[email protected]>
>
> When using non-standard rates, a relatively small amount of overclocking
> can make a big difference to a number of cases.

Applied, thanks.


Attachments:
(No filename) (245.00 B)
signature.asc (473.00 B)
Digital signature
Download all attachments