380 lines
9.5 KiB
C
380 lines
9.5 KiB
C
/*
|
|
* STMicroelectronics SOC audio clocks wrapper
|
|
*
|
|
* Copyright (c) 2010-2011 STMicroelectronics Limited
|
|
*
|
|
* Authors: Pawel Moll <pawel.moll@st.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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <asm/clock.h>
|
|
#include <asm/div64.h>
|
|
#include <sound/core.h>
|
|
|
|
#define COMPONENT clock
|
|
#include "common.h"
|
|
|
|
|
|
extern int snd_stm_debug_level;
|
|
|
|
#define to_snd_stm_clk(clk) \
|
|
container_of(clk, struct snd_stm_clk, clk)
|
|
|
|
struct snd_stm_clk_parent {
|
|
struct list_head list;
|
|
|
|
struct clk *parent;
|
|
|
|
int children_count, ref_count;
|
|
};
|
|
|
|
struct snd_stm_clk {
|
|
struct clk clk;
|
|
|
|
struct snd_stm_clk_parent *parent;
|
|
|
|
unsigned long nominal_rate;
|
|
int adjustment; /* Difference from the nominal rate, in ppm */
|
|
unsigned int enabled:1;
|
|
|
|
snd_stm_magic_field;
|
|
};
|
|
|
|
|
|
|
|
/* Adjustment ALSA control */
|
|
|
|
static int snd_stm_clk_adjustment_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = -999999;
|
|
uinfo->value.integer.max = 1000000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_stm_clk_adjustment_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk = snd_kcontrol_chip(kcontrol);
|
|
|
|
snd_stm_printd(1, "%s(kcontrol=0x%p, ucontrol=0x%p)\n",
|
|
__func__, kcontrol, ucontrol);
|
|
|
|
BUG_ON(!snd_stm_clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
ucontrol->value.integer.value[0] = snd_stm_clk->adjustment;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_stm_clk_adjustment_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk = snd_kcontrol_chip(kcontrol);
|
|
int old_adjustement;
|
|
|
|
snd_stm_printd(1, "%s(kcontrol=0x%p, ucontrol=0x%p)\n",
|
|
__func__, kcontrol, ucontrol);
|
|
|
|
BUG_ON(!snd_stm_clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
BUG_ON(ucontrol->value.integer.value[0] < -999999);
|
|
BUG_ON(ucontrol->value.integer.value[0] > 1000000);
|
|
|
|
old_adjustement = snd_stm_clk->adjustment;
|
|
snd_stm_clk->adjustment = ucontrol->value.integer.value[0];
|
|
|
|
if (snd_stm_clk->enabled && clk_set_rate(&snd_stm_clk->clk,
|
|
snd_stm_clk->nominal_rate) < 0)
|
|
return -EINVAL;
|
|
|
|
return old_adjustement != snd_stm_clk->adjustment;
|
|
}
|
|
|
|
static struct snd_kcontrol_new snd_stm_clk_adjustment_ctl = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
.name = "PCM Playback Oversampling Freq. Adjustment",
|
|
.info = snd_stm_clk_adjustment_info,
|
|
.get = snd_stm_clk_adjustment_get,
|
|
.put = snd_stm_clk_adjustment_put,
|
|
};
|
|
|
|
|
|
|
|
/* Clock interface */
|
|
|
|
static LIST_HEAD(snd_stm_clk_parents);
|
|
static DEFINE_SPINLOCK(snd_stm_clk_parents_lock);
|
|
|
|
int snd_stm_clk_enable(struct clk *clk)
|
|
{
|
|
int result = -EINVAL;
|
|
struct snd_stm_clk *snd_stm_clk;
|
|
|
|
snd_stm_printd(1, "%s(clk=%p)\n", __func__, clk);
|
|
|
|
BUG_ON(!clk);
|
|
snd_stm_clk = to_snd_stm_clk(clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
|
|
if (snd_stm_clk->parent->ref_count++ == 0) {
|
|
result = clk_enable(clk->parent);
|
|
if (result == 0)
|
|
snd_stm_clk->enabled = 1;
|
|
else
|
|
snd_stm_clk->parent->ref_count--;
|
|
}
|
|
|
|
BUG_ON(snd_stm_clk->parent->ref_count >
|
|
snd_stm_clk->parent->children_count);
|
|
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
int snd_stm_clk_disable(struct clk *clk)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk;
|
|
|
|
snd_stm_printd(1, "%s(clk=%p)\n", __func__, clk);
|
|
|
|
BUG_ON(!clk);
|
|
snd_stm_clk = to_snd_stm_clk(clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
|
|
if (--snd_stm_clk->parent->ref_count == 0) {
|
|
clk_disable(clk->parent);
|
|
snd_stm_clk->enabled = 0;
|
|
}
|
|
BUG_ON(snd_stm_clk->parent->ref_count < 0);
|
|
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int snd_stm_clk_set_rate(struct clk *clk, unsigned long rate)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk;
|
|
int rate_adjusted, rate_achieved;
|
|
int delta;
|
|
|
|
snd_stm_printd(1, "%s(clk=%p, rate=%lu)\n", __func__, clk, rate);
|
|
|
|
BUG_ON(!clk);
|
|
snd_stm_clk = to_snd_stm_clk(clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
/* User must enable the clock first */
|
|
if (!snd_stm_clk->enabled)
|
|
return -EAGAIN;
|
|
|
|
/* a
|
|
* F = f + --------- * f = f + d
|
|
* 1000000
|
|
*
|
|
* a
|
|
* d = --------- * f
|
|
* 1000000
|
|
*
|
|
* where:
|
|
* f - nominal rate
|
|
* a - adjustment in ppm (parts per milion)
|
|
* F - rate to be set in synthesizer
|
|
* d - delta (difference) between f and F
|
|
*/
|
|
if (snd_stm_clk->adjustment < 0) {
|
|
/* div64_64 operates on unsigned values... */
|
|
delta = -1;
|
|
snd_stm_clk->adjustment = -snd_stm_clk->adjustment;
|
|
} else {
|
|
delta = 1;
|
|
}
|
|
/* 500000 ppm is 0.5, which is used to round up values */
|
|
delta *= (int)div64_u64((uint64_t)rate *
|
|
(uint64_t)snd_stm_clk->adjustment + 500000,
|
|
1000000);
|
|
rate_adjusted = rate + delta;
|
|
|
|
snd_stm_printd(1, "Setting clock '%s' to rate %d.\n",
|
|
clk->parent->name, rate_adjusted);
|
|
/* adjusted rate should never be == 0 */
|
|
BUG_ON(rate_adjusted == 0);
|
|
if (clk_set_rate(snd_stm_clk->clk.parent, rate_adjusted) < 0) {
|
|
snd_stm_printe("Failed to set rate %d on clock '%s'!\n",
|
|
rate_adjusted, clk->parent->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rate_achieved = clk_get_rate(clk->parent);
|
|
snd_stm_printd(1, "Achieved rate %d.\n", rate_achieved);
|
|
/* using ALSA's adjustment control, we can modify the rate to be up to
|
|
twice as much as requested, but no more */
|
|
BUG_ON(rate_achieved > 2*rate);
|
|
delta = rate_achieved - rate;
|
|
if (delta < 0) {
|
|
/* div64_64 operates on unsigned values... */
|
|
delta = -delta;
|
|
snd_stm_clk->adjustment = -1;
|
|
} else {
|
|
snd_stm_clk->adjustment = 1;
|
|
}
|
|
/* frequency/2 is added to round up result */
|
|
snd_stm_clk->adjustment *= (int)div64_u64((uint64_t)delta * 1000000 +
|
|
rate / 2, rate);
|
|
|
|
snd_stm_clk->nominal_rate = rate;
|
|
clk->rate = rate_achieved;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct clk_ops snd_stm_clk_ops = {
|
|
.enable = snd_stm_clk_enable,
|
|
.disable = snd_stm_clk_disable,
|
|
.set_rate = snd_stm_clk_set_rate,
|
|
};
|
|
|
|
struct clk *snd_stm_clk_get(struct device *dev, const char *id,
|
|
struct snd_card *card, int card_device)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk;
|
|
struct snd_stm_clk_parent *snd_stm_clk_parent;
|
|
|
|
snd_stm_printd(0, "%s(dev=%p('%s'), id='%s')\n",
|
|
__func__, dev, dev_name(dev), id);
|
|
|
|
snd_stm_clk = kzalloc(sizeof(*snd_stm_clk), GFP_KERNEL);
|
|
if (!snd_stm_clk) {
|
|
snd_stm_printe("No memory for '%s'('%s') clock!\n",
|
|
dev_name(dev), id);
|
|
goto error_kzalloc_clk;
|
|
}
|
|
snd_stm_magic_set(snd_stm_clk);
|
|
|
|
snd_stm_clk->clk.ops = &snd_stm_clk_ops;
|
|
snd_stm_clk->clk.name = dev_name(dev);
|
|
|
|
/* Get the parent clock */
|
|
|
|
snd_stm_clk->clk.parent = clk_get(dev, id);
|
|
if (!snd_stm_clk->clk.parent || IS_ERR(snd_stm_clk->clk.parent)) {
|
|
snd_stm_printe("Can't get '%s' clock ('%s's parent)!\n",
|
|
id, dev_name(dev));
|
|
goto error_clk_get;
|
|
}
|
|
|
|
/* Find the parent clock description... */
|
|
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
|
|
list_for_each_entry(snd_stm_clk_parent, &snd_stm_clk_parents, list) {
|
|
if (snd_stm_clk_parent->parent == snd_stm_clk->clk.parent) {
|
|
snd_stm_clk->parent = snd_stm_clk_parent;
|
|
snd_stm_clk_parent->children_count++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
|
|
/* ... or allocate it now */
|
|
|
|
if (!snd_stm_clk->parent) {
|
|
snd_stm_clk->parent = kzalloc(sizeof(*snd_stm_clk->parent),
|
|
GFP_KERNEL);
|
|
if (!snd_stm_clk->parent) {
|
|
snd_stm_printe("No memory for '%s'('%s') parent!\n",
|
|
dev_name(dev), id);
|
|
goto error_kzalloc_parent;
|
|
}
|
|
snd_stm_clk->parent->parent = snd_stm_clk->clk.parent;
|
|
snd_stm_clk->parent->children_count = 1;
|
|
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
list_add_tail(&snd_stm_clk->parent->list, &snd_stm_clk_parents);
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
}
|
|
|
|
/* Register the clock "wrapper" */
|
|
|
|
if (clk_register(&snd_stm_clk->clk) < 0) {
|
|
snd_stm_printe("Failed to register '%s'('%s')\n",
|
|
dev_name(dev), id);
|
|
goto error_clk_register;
|
|
}
|
|
|
|
/* Rate adjustment ALSA control */
|
|
|
|
snd_stm_clk_adjustment_ctl.device = card_device;
|
|
if (snd_ctl_add(card, snd_ctl_new1(&snd_stm_clk_adjustment_ctl,
|
|
snd_stm_clk)) < 0) {
|
|
snd_stm_printe("Failed to add '%s'('%s') clock ALSA control!\n",
|
|
dev_name(dev), id);
|
|
goto error_snd_ctl_add;
|
|
}
|
|
snd_stm_clk_adjustment_ctl.index++;
|
|
|
|
return &snd_stm_clk->clk;
|
|
|
|
error_snd_ctl_add:
|
|
clk_unregister(&snd_stm_clk->clk);
|
|
error_clk_register:
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
if (--snd_stm_clk->parent->children_count == 0)
|
|
kfree(snd_stm_clk->parent);
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
error_kzalloc_parent:
|
|
clk_put(snd_stm_clk->clk.parent);
|
|
error_clk_get:
|
|
snd_stm_magic_clear(snd_stm_clk);
|
|
kfree(snd_stm_clk);
|
|
error_kzalloc_clk:
|
|
return NULL;
|
|
}
|
|
|
|
void snd_stm_clk_put(struct clk *clk)
|
|
{
|
|
struct snd_stm_clk *snd_stm_clk;
|
|
|
|
snd_stm_printd(0, "%s(clk=%p)\n", __func__, clk);
|
|
|
|
BUG_ON(!clk);
|
|
snd_stm_clk = to_snd_stm_clk(clk);
|
|
BUG_ON(!snd_stm_magic_valid(snd_stm_clk));
|
|
|
|
clk_unregister(clk);
|
|
spin_lock(&snd_stm_clk_parents_lock);
|
|
if (--snd_stm_clk->parent->children_count == 0)
|
|
kfree(snd_stm_clk->parent);
|
|
spin_unlock(&snd_stm_clk_parents_lock);
|
|
clk_put(clk->parent);
|
|
|
|
snd_stm_magic_clear(snd_stm_clk);
|
|
kfree(snd_stm_clk);
|
|
}
|