2020/11/12

WM8974 , 奇怪的 audio codec..

在linux kernel 中,很久以前,就有 wm8974 的 driver 了。
datasheet 看到這個不叫 i2c,叫 2 wire。

從 2 wire command diagram 來看.. 的確很奇怪:

address 部份跟一般 i2c 一樣,前 7 個是 address,最後一個 bit 是 read/write bit
接著是 reg , 只有 7 bit,所以最後一個 bit 是 data 的 highest bit

wm8974 的 reg 只有 7 bit,data 有 9 bit
register value 說明也真的有定義第9bit..

所以 i2c protocl:
1: chip address(7)+R/W(1)
2: reg index(7) + Data 8th bit(1)
3: Data 7-0 bit (8)
跟一般 I2C 裝置不一樣。

因為這樣,wm8974 沒有 read 動作。
也就是說,不能 read register。 --- 不然 protocol 要怎樣....



Linux driver 的部份:

古老的 wm8974 driver 使用自己提供的 i2c_read, i2c_write,看得出這個奇怪的動作:
static inline unsigned int wm8974_read_reg_cache(struct snd_soc_codec * codec,
	unsigned int reg)
{
	u16 *cache = codec->reg_cache;
	if (reg == WM8974_RESET)
		return 0;
	if (reg >= WM8974_CACHEREGNUM)
		return -1;
	return cache[reg];
}

/*
 * write wm8974 register cache
 */
static inline void wm8974_write_reg_cache(struct snd_soc_codec *codec,
	u16 reg, unsigned int value)
{
	u16 *cache = codec->reg_cache;
	if (reg >= WM8974_CACHEREGNUM)
		return;
	cache[reg] = value;
}

/*
 * write to the WM8974 register space
 */
static int wm8974_write(struct snd_soc_codec *codec, unsigned int reg,
	unsigned int value)
{
	u8 data[2];

	/* data is
	 *   D15..D9 WM8974 register offset
	 *   D8...D0 register data
	 */
	data[0] = (reg << 1) | ((value >> 8) & 0x0001);
	data[1] = value & 0x00ff;

	wm8974_write_reg_cache (codec, reg, value);
	if (codec->hw_write(codec->control_data, data, 2) == 2)
		return 0;
	else
		return -EIO;
}
i2c_write 內容有看到把 data shift 到 reg 一個bit
然後沒有提供 i2c_read

然後 driver 註冊時...
static int wm8974_init(struct snd_soc_device *socdev)
{
	struct snd_soc_codec *codec = socdev->codec;
	int ret = 0;

	codec->name = "WM8974";
	codec->owner = THIS_MODULE;
	codec->read = wm8974_read_reg_cache;
	codec->write = wm8974_write;
..
read 用的是 read_reg_cache



然後新版的kernel (3.1 以上),新增加了 regmap 這個 結構,
就是把 register map cache 起來,write 時同時更新 cache,
read 時就拿 cache 值,不用真的去 read。
-- 當然也可以真的去 read

同時 regmap 也增加了 reg_bits, val_bits 這兩個 properties。
所以新版的 wm8974 driver,已經有:
static const struct regmap_config wm8974_regmap = {
    .reg_bits = 7,
    .val_bits = 9,

    .max_register = WM8974_MONOMIX,
    .reg_defaults = wm8974_reg_defaults,
    .num_reg_defaults = ARRAY_SIZE(wm8974_reg_defaults),
    .cache_type = REGCACHE_FLAT,
};

比較漂亮的寫法就是用regmap : regmap, 有關沒對齊的 reg, value bits 的 i2c transfer function.

用了 regmap,cache type 用 FLAT,就會 使用 cache,這樣,read 就會從 cache 做,
這時候,要 dump register 舊只能用 regmap support 的地方:
# cat /sys/kernel/debug/regmap/2-001a/registers 
00: 0000
01: 002e
02: 0000
03: 0000
04: 0010
05: 0001
06: 0120
07: 0000
08: 0000
09: 0000
0a: 0041
0b: 00d7
0c: 0000
0d: 0000
0e: 0100
0f: 00ff
10: 0000
11: 0000
12: 012c
13: 002c
14: 002c
15: 002c
16: 002c
17: 0000
18: 0032
19: 0000
1a: 0000
1b: 0000
1c: 0000
1d: 0000
1e: 0000
1f: 0000
20: 0038
21: 000b
22: 0032
23: 0000
24: 001b
25: 0001
26: 0133
27: 0066
28: 0000
29: 0000
2a: 0000
2b: 0000
2c: 0003
2d: 0010
2e: 0000
2f: 0000
30: 0000
31: 0002
32: 0000
33: 0000
34: 0000
35: 0000
36: 0025
37: 0000
38: 0001

沒有留言:

張貼留言