Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Renesas R-Car DVC support |
| 3 | * |
| 4 | * Copyright (C) 2014 Renesas Solutions Corp. |
| 5 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License version 2 as |
| 9 | * published by the Free Software Foundation. |
| 10 | */ |
| 11 | #include "rsnd.h" |
| 12 | |
| 13 | #define RSND_DVC_NAME_SIZE 16 |
| 14 | #define RSND_DVC_VOLUME_MAX 100 |
| 15 | #define RSND_DVC_VOLUME_NUM 2 |
Kuninori Morimoto | 8aefda5 | 2014-05-22 23:25:43 -0700 | [diff] [blame] | 16 | |
| 17 | #define DVC_NAME "dvc" |
| 18 | |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 19 | struct rsnd_dvc { |
| 20 | struct rsnd_dvc_platform_info *info; /* rcar_snd.h */ |
| 21 | struct rsnd_mod mod; |
| 22 | struct clk *clk; |
| 23 | long volume[RSND_DVC_VOLUME_NUM]; |
| 24 | }; |
| 25 | |
| 26 | #define rsnd_mod_to_dvc(_mod) \ |
| 27 | container_of((_mod), struct rsnd_dvc, mod) |
| 28 | |
| 29 | #define for_each_rsnd_dvc(pos, priv, i) \ |
| 30 | for ((i) = 0; \ |
| 31 | ((i) < rsnd_dvc_nr(priv)) && \ |
| 32 | ((pos) = (struct rsnd_dvc *)(priv)->dvc + i); \ |
| 33 | i++) |
| 34 | |
| 35 | static void rsnd_dvc_volume_update(struct rsnd_mod *mod) |
| 36 | { |
| 37 | struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); |
| 38 | u32 max = (0x00800000 - 1); |
| 39 | u32 vol[RSND_DVC_VOLUME_NUM]; |
| 40 | int i; |
| 41 | |
| 42 | for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) |
| 43 | vol[i] = max / RSND_DVC_VOLUME_MAX * dvc->volume[i]; |
| 44 | |
| 45 | rsnd_mod_write(mod, DVC_VOL0R, vol[0]); |
| 46 | rsnd_mod_write(mod, DVC_VOL1R, vol[1]); |
| 47 | } |
| 48 | |
Kuninori Morimoto | 8aefda5 | 2014-05-22 23:25:43 -0700 | [diff] [blame] | 49 | static int rsnd_dvc_probe_gen2(struct rsnd_mod *mod, |
| 50 | struct rsnd_dai *rdai) |
| 51 | { |
| 52 | struct rsnd_priv *priv = rsnd_mod_to_priv(mod); |
| 53 | struct device *dev = rsnd_priv_to_dev(priv); |
| 54 | |
| 55 | dev_dbg(dev, "%s (Gen2) is probed\n", rsnd_mod_name(mod)); |
| 56 | |
| 57 | return 0; |
| 58 | } |
| 59 | |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 60 | static int rsnd_dvc_init(struct rsnd_mod *dvc_mod, |
| 61 | struct rsnd_dai *rdai) |
| 62 | { |
| 63 | struct rsnd_dvc *dvc = rsnd_mod_to_dvc(dvc_mod); |
| 64 | struct rsnd_dai_stream *io = rsnd_mod_to_io(dvc_mod); |
| 65 | struct rsnd_priv *priv = rsnd_mod_to_priv(dvc_mod); |
| 66 | struct rsnd_mod *src_mod = rsnd_io_to_mod_src(io); |
| 67 | struct device *dev = rsnd_priv_to_dev(priv); |
| 68 | int dvc_id = rsnd_mod_id(dvc_mod); |
| 69 | int src_id = rsnd_mod_id(src_mod); |
| 70 | u32 route[] = { |
| 71 | [0] = 0x30000, |
| 72 | [1] = 0x30001, |
| 73 | [2] = 0x40000, |
| 74 | [3] = 0x10000, |
| 75 | [4] = 0x20000, |
| 76 | [5] = 0x40100 |
| 77 | }; |
| 78 | |
| 79 | if (src_id >= ARRAY_SIZE(route)) { |
| 80 | dev_err(dev, "DVC%d isn't connected to SRC%d\n", dvc_id, src_id); |
| 81 | return -EINVAL; |
| 82 | } |
| 83 | |
| 84 | clk_prepare_enable(dvc->clk); |
| 85 | |
| 86 | /* |
| 87 | * fixme |
| 88 | * it doesn't support CTU/MIX |
| 89 | */ |
| 90 | rsnd_mod_write(dvc_mod, CMD_ROUTE_SLCT, route[src_id]); |
| 91 | |
| 92 | rsnd_mod_write(dvc_mod, DVC_SWRSR, 0); |
| 93 | rsnd_mod_write(dvc_mod, DVC_SWRSR, 1); |
| 94 | |
| 95 | rsnd_mod_write(dvc_mod, DVC_DVUIR, 1); |
| 96 | |
| 97 | rsnd_mod_write(dvc_mod, DVC_ADINR, rsnd_get_adinr(dvc_mod)); |
| 98 | |
| 99 | /* enable Volume */ |
| 100 | rsnd_mod_write(dvc_mod, DVC_DVUCR, 0x100); |
| 101 | |
| 102 | /* ch0/ch1 Volume */ |
| 103 | rsnd_dvc_volume_update(dvc_mod); |
| 104 | |
| 105 | rsnd_mod_write(dvc_mod, DVC_DVUIR, 0); |
| 106 | |
| 107 | rsnd_mod_write(dvc_mod, DVC_DVUER, 1); |
| 108 | |
| 109 | rsnd_adg_set_cmd_timsel_gen2(rdai, dvc_mod, io); |
| 110 | |
| 111 | return 0; |
| 112 | } |
| 113 | |
| 114 | static int rsnd_dvc_quit(struct rsnd_mod *mod, |
| 115 | struct rsnd_dai *rdai) |
| 116 | { |
| 117 | struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); |
| 118 | |
| 119 | clk_disable_unprepare(dvc->clk); |
| 120 | |
| 121 | return 0; |
| 122 | } |
| 123 | |
| 124 | static int rsnd_dvc_start(struct rsnd_mod *mod, |
| 125 | struct rsnd_dai *rdai) |
| 126 | { |
| 127 | rsnd_mod_write(mod, CMD_CTRL, 0x10); |
| 128 | |
| 129 | return 0; |
| 130 | } |
| 131 | |
| 132 | static int rsnd_dvc_stop(struct rsnd_mod *mod, |
| 133 | struct rsnd_dai *rdai) |
| 134 | { |
| 135 | rsnd_mod_write(mod, CMD_CTRL, 0); |
| 136 | |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | static int rsnd_dvc_volume_info(struct snd_kcontrol *kctrl, |
| 141 | struct snd_ctl_elem_info *uinfo) |
| 142 | { |
| 143 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| 144 | uinfo->count = RSND_DVC_VOLUME_NUM; |
| 145 | uinfo->value.integer.min = 0; |
| 146 | uinfo->value.integer.max = RSND_DVC_VOLUME_MAX; |
| 147 | |
| 148 | return 0; |
| 149 | } |
| 150 | |
| 151 | static int rsnd_dvc_volume_get(struct snd_kcontrol *kctrl, |
| 152 | struct snd_ctl_elem_value *ucontrol) |
| 153 | { |
| 154 | struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); |
| 155 | struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); |
| 156 | int i; |
| 157 | |
| 158 | for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) |
| 159 | ucontrol->value.integer.value[i] = dvc->volume[i]; |
| 160 | |
| 161 | return 0; |
| 162 | } |
| 163 | |
| 164 | static int rsnd_dvc_volume_put(struct snd_kcontrol *kctrl, |
| 165 | struct snd_ctl_elem_value *ucontrol) |
| 166 | { |
| 167 | struct rsnd_mod *mod = snd_kcontrol_chip(kctrl); |
| 168 | struct rsnd_dvc *dvc = rsnd_mod_to_dvc(mod); |
| 169 | int i, change = 0; |
| 170 | |
| 171 | for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) { |
| 172 | if (ucontrol->value.integer.value[i] < 0 || |
| 173 | ucontrol->value.integer.value[i] > RSND_DVC_VOLUME_MAX) |
| 174 | return -EINVAL; |
| 175 | |
| 176 | change |= (ucontrol->value.integer.value[i] != dvc->volume[i]); |
| 177 | } |
| 178 | |
| 179 | if (change) { |
| 180 | for (i = 0; i < RSND_DVC_VOLUME_NUM; i++) |
| 181 | dvc->volume[i] = ucontrol->value.integer.value[i]; |
| 182 | |
| 183 | rsnd_dvc_volume_update(mod); |
| 184 | } |
| 185 | |
| 186 | return change; |
| 187 | } |
| 188 | |
| 189 | static int rsnd_dvc_pcm_new(struct rsnd_mod *mod, |
| 190 | struct rsnd_dai *rdai, |
| 191 | struct snd_soc_pcm_runtime *rtd) |
| 192 | { |
| 193 | struct rsnd_dai_stream *io = rsnd_mod_to_io(mod); |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 194 | struct snd_card *card = rtd->card->snd_card; |
| 195 | struct snd_kcontrol *kctrl; |
| 196 | static struct snd_kcontrol_new knew = { |
| 197 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 198 | .info = rsnd_dvc_volume_info, |
| 199 | .get = rsnd_dvc_volume_get, |
| 200 | .put = rsnd_dvc_volume_put, |
| 201 | }; |
| 202 | int ret; |
| 203 | |
Kuninori Morimoto | 65f4599 | 2014-06-22 17:57:04 -0700 | [diff] [blame] | 204 | if (rsnd_dai_is_play(rdai, io)) |
| 205 | knew.name = "Playback Volume"; |
| 206 | else |
| 207 | knew.name = "Capture Volume"; |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 208 | |
| 209 | kctrl = snd_ctl_new1(&knew, mod); |
| 210 | if (!kctrl) |
| 211 | return -ENOMEM; |
| 212 | |
| 213 | ret = snd_ctl_add(card, kctrl); |
| 214 | if (ret < 0) |
| 215 | return ret; |
| 216 | |
| 217 | return 0; |
| 218 | } |
| 219 | |
| 220 | static struct rsnd_mod_ops rsnd_dvc_ops = { |
Kuninori Morimoto | 8aefda5 | 2014-05-22 23:25:43 -0700 | [diff] [blame] | 221 | .name = DVC_NAME, |
| 222 | .probe = rsnd_dvc_probe_gen2, |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 223 | .init = rsnd_dvc_init, |
| 224 | .quit = rsnd_dvc_quit, |
| 225 | .start = rsnd_dvc_start, |
| 226 | .stop = rsnd_dvc_stop, |
| 227 | .pcm_new = rsnd_dvc_pcm_new, |
| 228 | }; |
| 229 | |
| 230 | struct rsnd_mod *rsnd_dvc_mod_get(struct rsnd_priv *priv, int id) |
| 231 | { |
| 232 | if (WARN_ON(id < 0 || id >= rsnd_dvc_nr(priv))) |
| 233 | id = 0; |
| 234 | |
| 235 | return &((struct rsnd_dvc *)(priv->dvc) + id)->mod; |
| 236 | } |
| 237 | |
Kuninori Morimoto | 34cb612 | 2014-06-22 17:59:28 -0700 | [diff] [blame^] | 238 | static void rsnd_of_parse_dvc(struct platform_device *pdev, |
| 239 | const struct rsnd_of_data *of_data, |
| 240 | struct rsnd_priv *priv) |
| 241 | { |
| 242 | struct device_node *node; |
| 243 | struct rsnd_dvc_platform_info *dvc_info; |
| 244 | struct rcar_snd_info *info = rsnd_priv_to_info(priv); |
| 245 | struct device *dev = &pdev->dev; |
| 246 | int nr; |
| 247 | |
| 248 | if (!of_data) |
| 249 | return; |
| 250 | |
| 251 | node = of_get_child_by_name(dev->of_node, "rcar_sound,dvc"); |
| 252 | if (!node) |
| 253 | return; |
| 254 | |
| 255 | nr = of_get_child_count(node); |
| 256 | if (!nr) |
| 257 | goto rsnd_of_parse_dvc_end; |
| 258 | |
| 259 | dvc_info = devm_kzalloc(dev, |
| 260 | sizeof(struct rsnd_dvc_platform_info) * nr, |
| 261 | GFP_KERNEL); |
| 262 | if (!dvc_info) { |
| 263 | dev_err(dev, "dvc info allocation error\n"); |
| 264 | goto rsnd_of_parse_dvc_end; |
| 265 | } |
| 266 | |
| 267 | info->dvc_info = dvc_info; |
| 268 | info->dvc_info_nr = nr; |
| 269 | |
| 270 | rsnd_of_parse_dvc_end: |
| 271 | of_node_put(node); |
| 272 | } |
| 273 | |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 274 | int rsnd_dvc_probe(struct platform_device *pdev, |
| 275 | const struct rsnd_of_data *of_data, |
| 276 | struct rsnd_priv *priv) |
| 277 | { |
| 278 | struct rcar_snd_info *info = rsnd_priv_to_info(priv); |
| 279 | struct device *dev = rsnd_priv_to_dev(priv); |
| 280 | struct rsnd_dvc *dvc; |
| 281 | struct clk *clk; |
| 282 | char name[RSND_DVC_NAME_SIZE]; |
| 283 | int i, nr; |
| 284 | |
Kuninori Morimoto | 34cb612 | 2014-06-22 17:59:28 -0700 | [diff] [blame^] | 285 | rsnd_of_parse_dvc(pdev, of_data, priv); |
| 286 | |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 287 | nr = info->dvc_info_nr; |
| 288 | if (!nr) |
| 289 | return 0; |
| 290 | |
| 291 | /* This driver doesn't support Gen1 at this point */ |
| 292 | if (rsnd_is_gen1(priv)) { |
| 293 | dev_warn(dev, "CMD is not supported on Gen1\n"); |
| 294 | return -EINVAL; |
| 295 | } |
| 296 | |
| 297 | dvc = devm_kzalloc(dev, sizeof(*dvc) * nr, GFP_KERNEL); |
| 298 | if (!dvc) { |
| 299 | dev_err(dev, "CMD allocate failed\n"); |
| 300 | return -ENOMEM; |
| 301 | } |
| 302 | |
| 303 | priv->dvc_nr = nr; |
| 304 | priv->dvc = dvc; |
| 305 | |
| 306 | for_each_rsnd_dvc(dvc, priv, i) { |
Kuninori Morimoto | 8aefda5 | 2014-05-22 23:25:43 -0700 | [diff] [blame] | 307 | snprintf(name, RSND_DVC_NAME_SIZE, "%s.%d", |
| 308 | DVC_NAME, i); |
Kuninori Morimoto | bff58ea | 2014-05-08 17:44:49 -0700 | [diff] [blame] | 309 | |
| 310 | clk = devm_clk_get(dev, name); |
| 311 | if (IS_ERR(clk)) |
| 312 | return PTR_ERR(clk); |
| 313 | |
| 314 | dvc->info = &info->dvc_info[i]; |
| 315 | dvc->clk = clk; |
| 316 | |
| 317 | rsnd_mod_init(priv, &dvc->mod, &rsnd_dvc_ops, RSND_MOD_DVC, i); |
| 318 | |
| 319 | dev_dbg(dev, "CMD%d probed\n", i); |
| 320 | } |
| 321 | |
| 322 | return 0; |
| 323 | } |