轉載自:http://blog.csdn.net/azloong/article/details/7222799
2012已經到來,無論這個世界是否行將毀滅,在那之前的日子還得要繼續。
Android邁進了4.0,相應的Linux內核也進入了3.x時代。之後的一個工作估計要將2.6.32的驅動移植到3.x上面來。因此趁現在有空,看看alsa在這方面有什麼改動。
總的來說,架構大的改動是不大可能的。codec中幾個關鍵結構體沒有大的變化,如snd_soc_dai_ops、snd_soc_dai_driver(相當於2.6.32中的snd_soc_dai),倒是以前的snd_soc_codec_device重定義爲snd_soc_codec_driver,這個算是最明顯的。
註冊用結構體-snd_soc_codec_driver
2.6.32:
- /* codec device */
- struct snd_soc_codec_device {
- int (*probe)(struct platform_device *pdev);
- int (*remove)(struct platform_device *pdev);
- int (*suspend)(struct platform_device *pdev, pm_message_t state);
- int (*resume)(struct platform_device *pdev);
- };
3.1.1:
- /* codec driver */
- struct snd_soc_codec_driver {
- /* driver ops */
- int (*probe)(struct snd_soc_codec *);
- int (*remove)(struct snd_soc_codec *);
- int (*suspend)(struct snd_soc_codec *,
- pm_message_t state);
- int (*resume)(struct snd_soc_codec *);
- /* Default control and setup, added after probe() is run */
- const struct snd_kcontrol_new *controls;
- int num_controls;
- const struct snd_soc_dapm_widget *dapm_widgets;
- int num_dapm_widgets;
- const struct snd_soc_dapm_route *dapm_routes;
- int num_dapm_routes;
- /* codec wide operations */
- int (*set_sysclk)(struct snd_soc_codec *codec,
- int clk_id, unsigned int freq, int dir);
- int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
- unsigned int freq_in, unsigned int freq_out);
- /* codec IO */
- unsigned int (*read)(struct snd_soc_codec *, unsigned int);
- int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
- int (*display_register)(struct snd_soc_codec *, char *,
- size_t, unsigned int);
- int (*volatile_register)(struct snd_soc_codec *, unsigned int);
- int (*readable_register)(struct snd_soc_codec *, unsigned int);
- int (*writable_register)(struct snd_soc_codec *, unsigned int);
- short reg_cache_size;
- short reg_cache_step;
- short reg_word_size;
- const void *reg_cache_default;
- short reg_access_size;
- const struct snd_soc_reg_access *reg_access_default;
- enum snd_soc_compress_type compress_type;
- /* codec bias level */
- int (*set_bias_level)(struct snd_soc_codec *,
- enum snd_soc_bias_level level);
- void (*seq_notifier)(struct snd_soc_dapm_context *,
- enum snd_soc_dapm_type, int);
- /* probe ordering - for components with runtime dependencies */
- int probe_order;
- int remove_order;
- };
位於snd_soc_codec_driver中的一些codec IO成員函數和set_bias_level回調函數原來都放在另外一個結構體snd_soc_codec中,現在放置在這裏了,這是根據源碼結構調整的結果。事實真正需要設置的成員也不是很多,如下:
- static struct snd_soc_codec_driver soc_codec_dev_wm9713 = {
- .probe = wm9713_soc_probe,
- .remove = wm9713_soc_remove,
- .suspend = wm9713_soc_suspend,
- .resume = wm9713_soc_resume,
- .read = ac97_read,
- .write = ac97_write,
- .set_bias_level = wm9713_set_bias_level,
- .reg_cache_size = ARRAY_SIZE(wm9713_reg),
- .reg_word_size = sizeof(u16),
- .reg_cache_step = 2,
- .reg_cache_default = wm9713_reg,
- .dapm_widgets = wm9713_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets),
- .dapm_routes = wm9713_audio_map,
- .num_dapm_routes = ARRAY_SIZE(wm9713_audio_map),
- };
probe、remove、suspend、resume相信不用累述了,volatile_register函數判斷指定的寄存器是否volatile,reg_cache_size一般爲寄存器數目,reg_word_size爲寄存器的字長,reg_cache_default爲寄存器默認值配置表。
而dapm_widgets、dapm_routes就比較“犀利”了。之前的dapm widgets和routes分別通過函數snd_soc_dapm_new_controls和snd_soc_dapm_add_routes來註冊的(當然現在還保留這些接口),現在則可以填入到這個結構體,在soc-core裏註冊,省了不少功夫:
- static int soc_probe_codec(struct snd_soc_card *card,
- struct snd_soc_codec *codec)
- {
- //...
- if (driver->dapm_widgets)
- snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,
- driver->num_dapm_widgets);
- //...
- if (driver->controls)
- snd_soc_add_controls(codec, driver->controls,
- driver->num_controls);
- if (driver->dapm_routes)
- snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes,
- driver->num_dapm_routes);
- //...
- }
同時可看到dirver ops的函數參數有所不同了,以前是struct platform_device *pdev,現在改爲struct snd_soc_codec *codec,這個與codec的註冊函數snd_soc_register_codec和設備的drvdata有關,之後會逐一分析。
註冊函數-snd_soc_register_codec
對於snd_soc_codec_driver,之前需要EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713),然後在其他地方註冊,現在不用那麼麻煩了:
- static __devinit int wm9713_probe(struct platform_device *pdev)
- {
- return snd_soc_register_codec(&pdev->dev,
- &soc_codec_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
- }
隨後像普通的platform設備那樣註冊就行了。不再需要snd_soc_new_pcms註冊pcm、snd_soc_init_card註冊card。
snd_soc_register_codec函數原型:
- 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);
- static struct snd_soc_dai_driver wm9713_dai[] = {
- {
- .name = "wm9713-hifi",
- .ac97_control = 1,
- .playback = {
- .stream_name = "HiFi Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM9713_RATES,
- .formats = SND_SOC_STD_AC97_FMTS,},
- .capture = {
- .stream_name = "HiFi Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM9713_RATES,
- .formats = SND_SOC_STD_AC97_FMTS,},
- .ops = &wm9713_dai_ops_hifi,
- },
- //...
- //...
- };
注:之前的snd_soc_dai也需要EXPORT_SYMBOL_GPL的,然後在其他地方註冊的,現在改進了這點,減少了export的symbol,代碼架構更加清晰可靠。
設備私有數據-drvdata
在ALSA之CODEC分析中有提到“開始看到socdev = platform_get_drvdata(pdev)這句不免有點疑惑,到底pdev是在哪裏初始化好了?”,這個pdev就是drvdata。這個東東非常重要,它一般包含codec自定義的私有數據,如控制接口類型(I2C/SPI/AC97)、fll_in(FLL input frequency)、fll_out(FLL output frequency)等信息。因爲每種codec可能定義的私有數據體不同,而Linux內核喜歡抽象,所以就產生了drvdata。
之前drvdata的來龍去脈是百轉千迴繞吊瓶的,而現在版本是比較直觀的,且看分析:
- static inline void snd_soc_codec_set_drvdata(struct snd_soc_codec *codec,
- void *data)
- {
- dev_set_drvdata(codec->dev, data);
- }
- static inline void *snd_soc_codec_get_drvdata(struct snd_soc_codec *codec)
- {
- return dev_get_drvdata(codec->dev);
- }
soc.h裏實現兩個函數用於設置和獲取drvdata。snd_soc_codec是codec驅動裏最常使用的結構體,在各個操作函數均可以看到它的身影,所以選擇它和drvdata聯繫起來。
使用方法如下:
- static int wm9713_soc_probe(struct snd_soc_codec *codec)
- {
- struct wm9713_priv *wm9713;
- int ret = 0, reg;
- wm9713 = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL);
- if (wm9713 == NULL)
- return -ENOMEM;
- snd_soc_codec_set_drvdata(codec, wm9713);
- //...
- }
- static int wm9713_soc_remove(struct snd_soc_codec *codec)
- {
- struct wm9713_priv *wm9713 = snd_soc_codec_get_drvdata(codec);
- snd_soc_free_ac97_codec(codec);
- kfree(wm9713);
- return 0;
- }
設置控制接口-snd_soc_codec_set_cache_io
這個函數在以前版本也有的,但是當時沒有留意。現在發現這個函數很實用,只需要配置寄存器地址寬度、數據寬度、和控制接口類型,soc-io模塊就自動會選擇合適的控制接口函數。
- /**
- * snd_soc_codec_set_cache_io: Set up standard I/O functions.
- *
- * @codec: CODEC to configure.
- * @addr_bits: Number of bits of register address data.
- * @data_bits: Number of bits of data per register.
- * @control: Control bus used.
- *
- * Register formats are frequently shared between many I2C and SPI
- * devices. In order to promote code reuse the ASoC core provides
- * some standard implementations of CODEC read and write operations
- * which can be set up using this function.
- *
- * The caller is responsible for allocating and initialising the
- * actual cache.
- *
- * Note that at present this code cannot be used by CODECs with
- * volatile registers.
- */
- int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
- int addr_bits, int data_bits,
- enum snd_soc_control_type control)
- {
- int i;
- for (i = 0; i < ARRAY_SIZE(io_types); i++)
- if (io_types[i].addr_bits == addr_bits &&
- io_types[i].data_bits == data_bits)
- break;
- if (i == ARRAY_SIZE(io_types)) {
- printk(KERN_ERR
- "No I/O functions for %d bit address %d bit data\n",
- addr_bits, data_bits);
- return -EINVAL;
- }
- codec->write = io_types[i].write;
- codec->read = hw_read;
- codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;
- switch (control) {
- case SND_SOC_I2C:
- #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
- codec->hw_write = (hw_write_t)i2c_master_send;
- #endif
- if (io_types[i].i2c_read)
- codec->hw_read = io_types[i].i2c_read;
- codec->control_data = container_of(codec->dev,
- struct i2c_client,
- dev);
- break;
- case SND_SOC_SPI:
- #ifdef CONFIG_SPI_MASTER
- codec->hw_write = do_spi_write;
- #endif
- if (io_types[i].spi_read)
- codec->hw_read = io_types[i].spi_read;
- codec->control_data = container_of(codec->dev,
- struct spi_device,
- dev);
- break;
- }
- return 0;
- }
- EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);