wm8766.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. * ALSA driver for ICEnsemble VT17xx
  3. *
  4. * Lowlevel functions for WM8766 codec
  5. *
  6. * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. *
  22. */
  23. #include <linux/delay.h>
  24. #include <sound/core.h>
  25. #include <sound/control.h>
  26. #include <sound/tlv.h>
  27. #include "wm8766.h"
  28. /* low-level access */
  29. static void snd_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data)
  30. {
  31. if (addr < WM8766_REG_COUNT)
  32. wm->regs[addr] = data;
  33. wm->ops.write(wm, addr, data);
  34. }
  35. /* mixer controls */
  36. static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1);
  37. static struct snd_wm8766_ctl snd_wm8766_default_ctl[WM8766_CTL_COUNT] = {
  38. [WM8766_CTL_CH1_VOL] = {
  39. .name = "Channel 1 Playback Volume",
  40. .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
  41. .tlv = wm8766_tlv,
  42. .reg1 = WM8766_REG_DACL1,
  43. .reg2 = WM8766_REG_DACR1,
  44. .mask1 = WM8766_VOL_MASK,
  45. .mask2 = WM8766_VOL_MASK,
  46. .max = 0xff,
  47. .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
  48. },
  49. [WM8766_CTL_CH2_VOL] = {
  50. .name = "Channel 2 Playback Volume",
  51. .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
  52. .tlv = wm8766_tlv,
  53. .reg1 = WM8766_REG_DACL2,
  54. .reg2 = WM8766_REG_DACR2,
  55. .mask1 = WM8766_VOL_MASK,
  56. .mask2 = WM8766_VOL_MASK,
  57. .max = 0xff,
  58. .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
  59. },
  60. [WM8766_CTL_CH3_VOL] = {
  61. .name = "Channel 3 Playback Volume",
  62. .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
  63. .tlv = wm8766_tlv,
  64. .reg1 = WM8766_REG_DACL3,
  65. .reg2 = WM8766_REG_DACR3,
  66. .mask1 = WM8766_VOL_MASK,
  67. .mask2 = WM8766_VOL_MASK,
  68. .max = 0xff,
  69. .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
  70. },
  71. [WM8766_CTL_CH1_SW] = {
  72. .name = "Channel 1 Playback Switch",
  73. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  74. .reg1 = WM8766_REG_DACCTRL2,
  75. .mask1 = WM8766_DAC2_MUTE1,
  76. .flags = WM8766_FLAG_INVERT,
  77. },
  78. [WM8766_CTL_CH2_SW] = {
  79. .name = "Channel 2 Playback Switch",
  80. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  81. .reg1 = WM8766_REG_DACCTRL2,
  82. .mask1 = WM8766_DAC2_MUTE2,
  83. .flags = WM8766_FLAG_INVERT,
  84. },
  85. [WM8766_CTL_CH3_SW] = {
  86. .name = "Channel 3 Playback Switch",
  87. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  88. .reg1 = WM8766_REG_DACCTRL2,
  89. .mask1 = WM8766_DAC2_MUTE3,
  90. .flags = WM8766_FLAG_INVERT,
  91. },
  92. [WM8766_CTL_PHASE1_SW] = {
  93. .name = "Channel 1 Phase Invert Playback Switch",
  94. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  95. .reg1 = WM8766_REG_IFCTRL,
  96. .mask1 = WM8766_PHASE_INVERT1,
  97. },
  98. [WM8766_CTL_PHASE2_SW] = {
  99. .name = "Channel 2 Phase Invert Playback Switch",
  100. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  101. .reg1 = WM8766_REG_IFCTRL,
  102. .mask1 = WM8766_PHASE_INVERT2,
  103. },
  104. [WM8766_CTL_PHASE3_SW] = {
  105. .name = "Channel 3 Phase Invert Playback Switch",
  106. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  107. .reg1 = WM8766_REG_IFCTRL,
  108. .mask1 = WM8766_PHASE_INVERT3,
  109. },
  110. [WM8766_CTL_DEEMPH1_SW] = {
  111. .name = "Channel 1 Deemphasis Playback Switch",
  112. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  113. .reg1 = WM8766_REG_DACCTRL2,
  114. .mask1 = WM8766_DAC2_DEEMP1,
  115. },
  116. [WM8766_CTL_DEEMPH2_SW] = {
  117. .name = "Channel 2 Deemphasis Playback Switch",
  118. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  119. .reg1 = WM8766_REG_DACCTRL2,
  120. .mask1 = WM8766_DAC2_DEEMP2,
  121. },
  122. [WM8766_CTL_DEEMPH3_SW] = {
  123. .name = "Channel 3 Deemphasis Playback Switch",
  124. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  125. .reg1 = WM8766_REG_DACCTRL2,
  126. .mask1 = WM8766_DAC2_DEEMP3,
  127. },
  128. [WM8766_CTL_IZD_SW] = {
  129. .name = "Infinite Zero Detect Playback Switch",
  130. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  131. .reg1 = WM8766_REG_DACCTRL1,
  132. .mask1 = WM8766_DAC_IZD,
  133. },
  134. [WM8766_CTL_ZC_SW] = {
  135. .name = "Zero Cross Detect Playback Switch",
  136. .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
  137. .reg1 = WM8766_REG_DACCTRL2,
  138. .mask1 = WM8766_DAC2_ZCD,
  139. .flags = WM8766_FLAG_INVERT,
  140. },
  141. };
  142. /* exported functions */
  143. void snd_wm8766_init(struct snd_wm8766 *wm)
  144. {
  145. int i;
  146. static const u16 default_values[] = {
  147. 0x000, 0x100,
  148. 0x120, 0x000,
  149. 0x000, 0x100, 0x000, 0x100, 0x000,
  150. 0x000, 0x080,
  151. };
  152. memcpy(wm->ctl, snd_wm8766_default_ctl, sizeof(wm->ctl));
  153. snd_wm8766_write(wm, WM8766_REG_RESET, 0x00); /* reset */
  154. udelay(10);
  155. /* load defaults */
  156. for (i = 0; i < ARRAY_SIZE(default_values); i++)
  157. snd_wm8766_write(wm, i, default_values[i]);
  158. }
  159. void snd_wm8766_resume(struct snd_wm8766 *wm)
  160. {
  161. int i;
  162. for (i = 0; i < WM8766_REG_COUNT; i++)
  163. snd_wm8766_write(wm, i, wm->regs[i]);
  164. }
  165. void snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac)
  166. {
  167. u16 val = wm->regs[WM8766_REG_IFCTRL] & ~WM8766_IF_MASK;
  168. dac &= WM8766_IF_MASK;
  169. snd_wm8766_write(wm, WM8766_REG_IFCTRL, val | dac);
  170. }
  171. void snd_wm8766_volume_restore(struct snd_wm8766 *wm)
  172. {
  173. u16 val = wm->regs[WM8766_REG_DACR1];
  174. /* restore volume after MCLK stopped */
  175. snd_wm8766_write(wm, WM8766_REG_DACR1, val | WM8766_VOL_UPDATE);
  176. }
  177. /* mixer callbacks */
  178. static int snd_wm8766_volume_info(struct snd_kcontrol *kcontrol,
  179. struct snd_ctl_elem_info *uinfo)
  180. {
  181. struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
  182. int n = kcontrol->private_value;
  183. uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
  184. uinfo->count = (wm->ctl[n].flags & WM8766_FLAG_STEREO) ? 2 : 1;
  185. uinfo->value.integer.min = wm->ctl[n].min;
  186. uinfo->value.integer.max = wm->ctl[n].max;
  187. return 0;
  188. }
  189. static int snd_wm8766_enum_info(struct snd_kcontrol *kcontrol,
  190. struct snd_ctl_elem_info *uinfo)
  191. {
  192. struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
  193. int n = kcontrol->private_value;
  194. return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
  195. wm->ctl[n].enum_names);
  196. }
  197. static int snd_wm8766_ctl_get(struct snd_kcontrol *kcontrol,
  198. struct snd_ctl_elem_value *ucontrol)
  199. {
  200. struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
  201. int n = kcontrol->private_value;
  202. u16 val1, val2;
  203. if (wm->ctl[n].get)
  204. wm->ctl[n].get(wm, &val1, &val2);
  205. else {
  206. val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
  207. val1 >>= __ffs(wm->ctl[n].mask1);
  208. if (wm->ctl[n].flags & WM8766_FLAG_STEREO) {
  209. val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
  210. val2 >>= __ffs(wm->ctl[n].mask2);
  211. if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE)
  212. val2 &= ~WM8766_VOL_UPDATE;
  213. }
  214. }
  215. if (wm->ctl[n].flags & WM8766_FLAG_INVERT) {
  216. val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
  217. if (wm->ctl[n].flags & WM8766_FLAG_STEREO)
  218. val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
  219. }
  220. ucontrol->value.integer.value[0] = val1;
  221. if (wm->ctl[n].flags & WM8766_FLAG_STEREO)
  222. ucontrol->value.integer.value[1] = val2;
  223. return 0;
  224. }
  225. static int snd_wm8766_ctl_put(struct snd_kcontrol *kcontrol,
  226. struct snd_ctl_elem_value *ucontrol)
  227. {
  228. struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
  229. int n = kcontrol->private_value;
  230. u16 val, regval1, regval2;
  231. /* this also works for enum because value is an union */
  232. regval1 = ucontrol->value.integer.value[0];
  233. regval2 = ucontrol->value.integer.value[1];
  234. if (wm->ctl[n].flags & WM8766_FLAG_INVERT) {
  235. regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
  236. regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
  237. }
  238. if (wm->ctl[n].set)
  239. wm->ctl[n].set(wm, regval1, regval2);
  240. else {
  241. val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
  242. val |= regval1 << __ffs(wm->ctl[n].mask1);
  243. /* both stereo controls in one register */
  244. if (wm->ctl[n].flags & WM8766_FLAG_STEREO &&
  245. wm->ctl[n].reg1 == wm->ctl[n].reg2) {
  246. val &= ~wm->ctl[n].mask2;
  247. val |= regval2 << __ffs(wm->ctl[n].mask2);
  248. }
  249. snd_wm8766_write(wm, wm->ctl[n].reg1, val);
  250. /* stereo controls in different registers */
  251. if (wm->ctl[n].flags & WM8766_FLAG_STEREO &&
  252. wm->ctl[n].reg1 != wm->ctl[n].reg2) {
  253. val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
  254. val |= regval2 << __ffs(wm->ctl[n].mask2);
  255. if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE)
  256. val |= WM8766_VOL_UPDATE;
  257. snd_wm8766_write(wm, wm->ctl[n].reg2, val);
  258. }
  259. }
  260. return 0;
  261. }
  262. static int snd_wm8766_add_control(struct snd_wm8766 *wm, int num)
  263. {
  264. struct snd_kcontrol_new cont;
  265. struct snd_kcontrol *ctl;
  266. memset(&cont, 0, sizeof(cont));
  267. cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
  268. cont.private_value = num;
  269. cont.name = wm->ctl[num].name;
  270. cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
  271. if (wm->ctl[num].flags & WM8766_FLAG_LIM ||
  272. wm->ctl[num].flags & WM8766_FLAG_ALC)
  273. cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
  274. cont.tlv.p = NULL;
  275. cont.get = snd_wm8766_ctl_get;
  276. cont.put = snd_wm8766_ctl_put;
  277. switch (wm->ctl[num].type) {
  278. case SNDRV_CTL_ELEM_TYPE_INTEGER:
  279. cont.info = snd_wm8766_volume_info;
  280. cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
  281. cont.tlv.p = wm->ctl[num].tlv;
  282. break;
  283. case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
  284. wm->ctl[num].max = 1;
  285. if (wm->ctl[num].flags & WM8766_FLAG_STEREO)
  286. cont.info = snd_ctl_boolean_stereo_info;
  287. else
  288. cont.info = snd_ctl_boolean_mono_info;
  289. break;
  290. case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
  291. cont.info = snd_wm8766_enum_info;
  292. break;
  293. default:
  294. return -EINVAL;
  295. }
  296. ctl = snd_ctl_new1(&cont, wm);
  297. if (!ctl)
  298. return -ENOMEM;
  299. wm->ctl[num].kctl = ctl;
  300. return snd_ctl_add(wm->card, ctl);
  301. }
  302. int snd_wm8766_build_controls(struct snd_wm8766 *wm)
  303. {
  304. int err, i;
  305. for (i = 0; i < WM8766_CTL_COUNT; i++)
  306. if (wm->ctl[i].name) {
  307. err = snd_wm8766_add_control(wm, i);
  308. if (err < 0)
  309. return err;
  310. }
  311. return 0;
  312. }