コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2017年6月16日 >>> 2017年6月16日
link もっと後

2017年6月16日

ALSAのミキサーとregmapその2 - kcontrolとregmapの関係

目次: ALSA

メモ代わりの超マニアックな話です。kcontrolについては前回を参照。

ALSAのkcontrolはレジスタのアドレスと値の範囲をALSAの共通レイヤに渡すだけで、ミキサー機能から、ハードウェアレジスタの値が書き換えられます。レジスタの値をアクセスする関数を一切書いていないにも関わらず、です。

最初、この仕組みがわからず、超不思議でした。どうなってるんでしょうか?

kcontrolとregmap

まずkcontrolが何をしているか見てみます。といってもkcontrol側は深追いせず、レジスタアクセスのみ調べます。ちなみにkcontrol_newの何がどうなってミキサーに繋がるか調べるのは、かなり大変そうです……。

kcontrolの宣言とSOC_DOUBLEの実装

//linux-4.4/sound/soc/codecs/tas571x.c

static const struct snd_kcontrol_new tas5717_controls[] = {
...
        SOC_DOUBLE("Speaker Switch",
                   TAS571X_SOFT_MUTE_REG,
                   TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
                   1, 1),


//linux-4.4/include/sound/soc.h

#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
        .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \    //★たぶんこれ使って読んでるんだろう★
        .put = snd_soc_put_volsw, \
        .private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \
                                          max, invert, 0) }


//linux-4.4/sound/soc/soc-ops.c

int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_value *ucontrol)
{
...
        ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val);
        if (ret)
                return ret;
...


//linux-4.4/sound/soc/soc-ops.c

static int snd_soc_read_signed(struct snd_soc_component *component,
        unsigned int reg, unsigned int mask, unsigned int shift,
        unsigned int sign_bit, int *signed_val)
{
...
        ret = snd_soc_component_read(component, reg, &val);
        if (ret < 0)
                return ret;


//linux-4.4/sound/soc/soc-io.c

int snd_soc_component_read(struct snd_soc_component *component,
        unsigned int reg, unsigned int *val)
{
        int ret;

        if (component->regmap)
                ret = regmap_read(component->regmap, reg, val);    //★regmapで読み出すか★
        else if (component->read)
                ret = component->read(component, reg, val);    //★regmapが無くて、独自のレジスタアクセス関数があればそちらを使う★
        else
                ret = -EIO;    //★どちらもなければエラー★

        return ret;
}

はい、やっと出ましたregmapです。コードを見るとcomponent->regmapとやらを参照していますが、こんなものを設定した覚えはありません。どこから来たのでしょうか?

答えは前回の最後に呼んでいたsnd_soc_register_codec() にあります。

snd_soc_register_codec

答えはsnd_soc_register_codec() にある、あるんですけど、ちょっと面倒くさいです。

snd_soc_register_codec

//linux-4.4/sound/soc/soc-core.c

int snd_soc_register_codec(struct device *dev,
                           const struct snd_soc_codec_driver *codec_drv,
                           struct snd_soc_dai_driver *dai_drv,
                           int num_dai)
{
...
        struct snd_soc_codec *codec;
...
        codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
        if (codec == NULL)
                return -ENOMEM;

        codec->component.codec = codec;

        ret = snd_soc_component_initialize(&codec->component,    //★注目その2★
                        &codec_drv->component_driver, dev);
        if (ret)
                goto err_free;
...
        mutex_lock(&client_mutex);
        snd_soc_component_add_unlocked(&codec->component);    //★注目その1★
        list_add(&codec->list, &codec_list);
        mutex_unlock(&client_mutex);
...

順序が逆転しますが、ゴールから逆にたどる形で説明します。まずcomponent->regmapがどこから来たのか?を追います。答えはsnd_soc_component_add_unlocked() にあります。

component->regmapが設定されるまで

//linux-4.4/sound/soc/soc-core.c

static void snd_soc_component_add_unlocked(struct snd_soc_component *component)
{
        if (!component->write && !component->read) {
                if (!component->regmap)
                        component->regmap = dev_get_regmap(component->dev, NULL);
...


//linux-4.4/drivers/base/regmap/regmap.c

struct regmap *dev_get_regmap(struct device *dev, const char *name)
{
        struct regmap **r = devres_find(dev, dev_get_regmap_release,
                                        dev_get_regmap_match, (void *)name);
...


//linux-4.4/drivers/base/regmap/regmap.c

static int dev_get_regmap_match(struct device *dev, void *res, void *data)
{
        struct regmap **r = res;
...
        /* If the user didn't specify a name match any */
        if (data)
                return (*r)->name == data;
        else
                return 1;
...


//linux-4.4/drivers/base/devres.c

void * devres_find(struct device *dev, dr_release_t release,
                   dr_match_t match, void *match_data)
{
        struct devres *dr;
...
        dr = find_dr(dev, release, match, match_data);
...


//linux-4.4/drivers/base/devres.c

static struct devres *find_dr(struct device *dev, dr_release_t release,
                              dr_match_t match, void *match_data)
{
        struct devres_node *node;

        list_for_each_entry_reverse(node, &dev->devres_head, entry) {
                struct devres *dr = container_of(node, struct devres, node);

                if (node->release != release)
                        continue;
                //★今回はmatchにdev_get_regmap_match() を渡しています★
                //★またmatch_dataにはNULLを渡しています★
                //★dev_get_regmap_match() の第3引数がNULLの場合、最初に見つけたregmapにヒットします★
                if (match && !match(dev, dr->data, match_data))
                        continue;
                return dr;
        }

        return NULL;
}

以上のようにcomponent->regmapはdev_get_regmap(component->dev, NULL) の結果を使っています。dev_get_regmap() の第2引数NULLなので、デバイスのリソース一覧(dev->devres_head)から、とにかく何でも良いので最初に見つけたregmapを返してくれという意味になります。

最初見たとき、関数名と中身が全然違うので、分かるわけねぇだろこんなの…って思いました。

次にcomponent->devはどこから来たのか?を追います。答えはsnd_soc_component_initialize() にあります。

component->regmapが設定されるまで

//linux-4.4/sound/soc/soc-core.c

static int snd_soc_component_initialize(struct snd_soc_component *component,
        const struct snd_soc_component_driver *driver, struct device *dev)
{
...
        component->dev = dev;    //★これです★
        component->driver = driver;
        component->probe = component->driver->probe;
        component->remove = component->driver->remove;

こちらはそんなに難しくないですね。

最後の疑問

ここまで調べてきて分かったことは、下記の通りです。

  • ALSAのkcontrolはsnd_soc_component_read() という関数でハードウェアのレジスタにアクセスしているようだ
  • snd_component_read() はcomponent->regmapを使う
  • component->regmapはデバイスcomponent->devのリソースリストから探す
  • component->devはsnd_soc_register_codec() に渡したdevを使う

ここで最後の疑問です。regmapをdevresに追加する方法は?わからないですね。追加した覚えはありません。

でも絶対何かしているハズです。もう一回TAS5086のドライバに戻ってみます。

TAS5086再び

//linux-4.4/sound/soc/codecs/tas5086.c

static int tas5086_i2c_probe(struct i2c_client *i2c,
                             const struct i2c_device_id *id)
{
...
        priv->regmap = devm_regmap_init(dev, NULL, i2c, &tas5086_regmap);    //★もしかしてこれ?★
        if (IS_ERR(priv->regmap)) {
                ret = PTR_ERR(priv->regmap);
                dev_err(&i2c->dev, "Failed to create regmap: %d\n", ret);
                return ret;
        }
...
        if (ret == 0)
                ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_tas5086,    //★snd_soc_codec_driverのポインタをここに渡している★
                                             &tas5086_dai, 1);

        return ret;
}

ざっと見てもregmapに何かしているのはdevm_regmap_init() だけです。どうしてdevm_regmap_init() なのでしょうか?どうしてregmap_init() ではダメなのでしょうか?

デバイスのリソースリストとregmapの関係

//linux-4.4/include/linux/regmap.h

#define devm_regmap_init(dev, bus, bus_context, config)                 \
        __regmap_lockdep_wrapper(__devm_regmap_init, #config,           \
                                dev, bus, bus_context, config)
//★__regmap_lockdep_wrapper() はロックを取って __devm_regmap_init() を呼ぶマクロ★


//linux-4.4/drivers/base/regmap/regmap.c

struct regmap *__devm_regmap_init(struct device *dev,
                                  const struct regmap_bus *bus,
                                  void *bus_context,
                                  const struct regmap_config *config,
                                  struct lock_class_key *lock_key,
                                  const char *lock_name)
{
        struct regmap **ptr, *regmap;

        ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL);
        if (!ptr)
                return ERR_PTR(-ENOMEM);

        //★regmap_init() も結局 __regmap_init() を呼ぶだけなのに何が違う?★
        regmap = __regmap_init(dev, bus, bus_context, config,
                               lock_key, lock_name);
        if (!IS_ERR(regmap)) {
                *ptr = regmap;
                devres_add(dev, ptr);    //★もしかしてこれ?★
        } else {
                devres_free(ptr);
        }

        return regmap;
}


//linux-4.4/drivers/base/devres.c

void devres_add(struct device *dev, void *res)
{
        struct devres *dr = container_of(res, struct devres, data);
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);
        add_dr(dev, &dr->node);
        spin_unlock_irqrestore(&dev->devres_lock, flags);
}


//linux-4.4/drivers/base/devres.c

static void add_dr(struct device *dev, struct devres_node *node)
{
        devres_log(dev, node, "ADD");
        BUG_ON(!list_empty(&node->entry));
        list_add_tail(&node->entry, &dev->devres_head);    //★ここでリストに追加している!★
}

以上のように、devm_regmap_init() もregmap_init() もregmap初期化という役割から見ると同等ですが、devm_regmap_init() はデバイスのリソースリスト(dev->devres_head)にregmapを登録するという役割も果たしています。

まとめ

まとめると、

  • ALSAのkcontrolを使うときはregmap_init() ではなく、必ずdevm_regmap_init() を使い、regmapをデバイスのリソースリストに足しましょう。
  • regmapを登録したデバイスをsnd_soc_register_codec() に渡しましょう。別のデバイスを渡してはいけません。

このルールだけ見れば難しくないですが、なぜこのルールなのか調べると、結構複雑でした。面白かったです。

編集者:すずき(2022/05/22 15:19)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2017年6月16日 >>> 2017年6月16日
link もっと後

管理用メニュー

link 記事を新規作成

<2017>
<<<06>>>
----123
45678910
11121314151617
18192021222324
252627282930-

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20