| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * AK4104 ALSA SoC (ASoC) driver | 
|  | 3 | * | 
|  | 4 | * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> | 
|  | 5 | * | 
|  | 6 | *  This program is free software; you can redistribute  it and/or modify it | 
|  | 7 | *  under the terms of  the GNU General  Public License as published by the | 
|  | 8 | *  Free Software Foundation;  either version 2 of the  License, or (at your | 
|  | 9 | *  option) any later version. | 
|  | 10 | */ | 
|  | 11 |  | 
|  | 12 | #include <linux/module.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 13 | #include <linux/slab.h> | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 14 | #include <sound/core.h> | 
|  | 15 | #include <sound/soc.h> | 
|  | 16 | #include <sound/initval.h> | 
|  | 17 | #include <linux/spi/spi.h> | 
| Daniel Mack | 385a4c2 | 2012-11-14 18:28:39 +0800 | [diff] [blame] | 18 | #include <linux/of_device.h> | 
|  | 19 | #include <linux/of_gpio.h> | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 20 | #include <sound/asoundef.h> | 
|  | 21 |  | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 22 | /* AK4104 registers addresses */ | 
|  | 23 | #define AK4104_REG_CONTROL1		0x00 | 
|  | 24 | #define AK4104_REG_RESERVED		0x01 | 
|  | 25 | #define AK4104_REG_CONTROL2		0x02 | 
|  | 26 | #define AK4104_REG_TX			0x03 | 
|  | 27 | #define AK4104_REG_CHN_STATUS(x)	((x) + 0x04) | 
|  | 28 | #define AK4104_NUM_REGS			10 | 
|  | 29 |  | 
|  | 30 | #define AK4104_REG_MASK			0x1f | 
|  | 31 | #define AK4104_READ			0xc0 | 
|  | 32 | #define AK4104_WRITE			0xe0 | 
|  | 33 | #define AK4104_RESERVED_VAL		0x5b | 
|  | 34 |  | 
|  | 35 | /* Bit masks for AK4104 registers */ | 
|  | 36 | #define AK4104_CONTROL1_RSTN		(1 << 0) | 
|  | 37 | #define AK4104_CONTROL1_PW		(1 << 1) | 
|  | 38 | #define AK4104_CONTROL1_DIF0		(1 << 2) | 
|  | 39 | #define AK4104_CONTROL1_DIF1		(1 << 3) | 
|  | 40 |  | 
|  | 41 | #define AK4104_CONTROL2_SEL0		(1 << 0) | 
|  | 42 | #define AK4104_CONTROL2_SEL1		(1 << 1) | 
|  | 43 | #define AK4104_CONTROL2_MODE		(1 << 2) | 
|  | 44 |  | 
|  | 45 | #define AK4104_TX_TXE			(1 << 0) | 
|  | 46 | #define AK4104_TX_V			(1 << 1) | 
|  | 47 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 48 | #define DRV_NAME "ak4104-codec" | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 49 |  | 
|  | 50 | struct ak4104_private { | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 51 | struct regmap *regmap; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 52 | }; | 
|  | 53 |  | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 54 | static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai, | 
|  | 55 | unsigned int format) | 
|  | 56 | { | 
|  | 57 | struct snd_soc_codec *codec = codec_dai->codec; | 
|  | 58 | int val = 0; | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 59 | int ret; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 60 |  | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 61 | /* set DAI format */ | 
|  | 62 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { | 
|  | 63 | case SND_SOC_DAIFMT_RIGHT_J: | 
|  | 64 | break; | 
|  | 65 | case SND_SOC_DAIFMT_LEFT_J: | 
|  | 66 | val |= AK4104_CONTROL1_DIF0; | 
|  | 67 | break; | 
|  | 68 | case SND_SOC_DAIFMT_I2S: | 
|  | 69 | val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1; | 
|  | 70 | break; | 
|  | 71 | default: | 
|  | 72 | dev_err(codec->dev, "invalid dai format\n"); | 
|  | 73 | return -EINVAL; | 
|  | 74 | } | 
|  | 75 |  | 
|  | 76 | /* This device can only be slave */ | 
|  | 77 | if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) | 
|  | 78 | return -EINVAL; | 
|  | 79 |  | 
| Mark Brown | afad95f | 2012-02-17 12:04:41 -0800 | [diff] [blame] | 80 | ret = snd_soc_update_bits(codec, AK4104_REG_CONTROL1, | 
|  | 81 | AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1, | 
|  | 82 | val); | 
|  | 83 | if (ret < 0) | 
|  | 84 | return ret; | 
|  | 85 |  | 
|  | 86 | return 0; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 87 | } | 
|  | 88 |  | 
|  | 89 | static int ak4104_hw_params(struct snd_pcm_substream *substream, | 
|  | 90 | struct snd_pcm_hw_params *params, | 
|  | 91 | struct snd_soc_dai *dai) | 
|  | 92 | { | 
| Mark Brown | e6968a1 | 2012-04-04 15:58:16 +0100 | [diff] [blame] | 93 | struct snd_soc_codec *codec = dai->codec; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 94 | int val = 0; | 
|  | 95 |  | 
|  | 96 | /* set the IEC958 bits: consumer mode, no copyright bit */ | 
|  | 97 | val |= IEC958_AES0_CON_NOT_COPYRIGHT; | 
| Mark Brown | 34baf22 | 2012-02-17 12:05:51 -0800 | [diff] [blame] | 98 | snd_soc_write(codec, AK4104_REG_CHN_STATUS(0), val); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 99 |  | 
|  | 100 | val = 0; | 
|  | 101 |  | 
|  | 102 | switch (params_rate(params)) { | 
| Daniel Mack | 08201de | 2012-10-07 17:51:23 +0200 | [diff] [blame] | 103 | case 22050: | 
|  | 104 | val |= IEC958_AES3_CON_FS_22050; | 
|  | 105 | break; | 
|  | 106 | case 24000: | 
|  | 107 | val |= IEC958_AES3_CON_FS_24000; | 
|  | 108 | break; | 
|  | 109 | case 32000: | 
|  | 110 | val |= IEC958_AES3_CON_FS_32000; | 
|  | 111 | break; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 112 | case 44100: | 
|  | 113 | val |= IEC958_AES3_CON_FS_44100; | 
|  | 114 | break; | 
|  | 115 | case 48000: | 
|  | 116 | val |= IEC958_AES3_CON_FS_48000; | 
|  | 117 | break; | 
| Daniel Mack | 08201de | 2012-10-07 17:51:23 +0200 | [diff] [blame] | 118 | case 88200: | 
|  | 119 | val |= IEC958_AES3_CON_FS_88200; | 
|  | 120 | break; | 
|  | 121 | case 96000: | 
|  | 122 | val |= IEC958_AES3_CON_FS_96000; | 
|  | 123 | break; | 
|  | 124 | case 176400: | 
|  | 125 | val |= IEC958_AES3_CON_FS_176400; | 
|  | 126 | break; | 
|  | 127 | case 192000: | 
|  | 128 | val |= IEC958_AES3_CON_FS_192000; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 129 | break; | 
|  | 130 | default: | 
|  | 131 | dev_err(codec->dev, "unsupported sampling rate\n"); | 
|  | 132 | return -EINVAL; | 
|  | 133 | } | 
|  | 134 |  | 
| Mark Brown | 34baf22 | 2012-02-17 12:05:51 -0800 | [diff] [blame] | 135 | return snd_soc_write(codec, AK4104_REG_CHN_STATUS(3), val); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 136 | } | 
|  | 137 |  | 
| Lars-Peter Clausen | 85e7652 | 2011-11-23 11:40:40 +0100 | [diff] [blame] | 138 | static const struct snd_soc_dai_ops ak4101_dai_ops = { | 
| Mark Brown | 65ec1cd | 2009-03-11 16:51:31 +0000 | [diff] [blame] | 139 | .hw_params = ak4104_hw_params, | 
|  | 140 | .set_fmt = ak4104_set_dai_fmt, | 
|  | 141 | }; | 
|  | 142 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 143 | static struct snd_soc_dai_driver ak4104_dai = { | 
|  | 144 | .name = "ak4104-hifi", | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 145 | .playback = { | 
|  | 146 | .stream_name = "Playback", | 
|  | 147 | .channels_min = 2, | 
|  | 148 | .channels_max = 2, | 
| Daniel Mack | 617b14c | 2010-01-13 11:25:05 +0100 | [diff] [blame] | 149 | .rates = SNDRV_PCM_RATE_8000_192000, | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 150 | .formats = SNDRV_PCM_FMTBIT_S16_LE  | | 
|  | 151 | SNDRV_PCM_FMTBIT_S24_3LE | | 
|  | 152 | SNDRV_PCM_FMTBIT_S24_LE | 
|  | 153 | }, | 
| Mark Brown | 65ec1cd | 2009-03-11 16:51:31 +0000 | [diff] [blame] | 154 | .ops = &ak4101_dai_ops, | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 155 | }; | 
|  | 156 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 157 | static int ak4104_probe(struct snd_soc_codec *codec) | 
|  | 158 | { | 
|  | 159 | struct ak4104_private *ak4104 = snd_soc_codec_get_drvdata(codec); | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 160 | int ret; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 161 |  | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 162 | codec->control_data = ak4104->regmap; | 
|  | 163 | ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); | 
|  | 164 | if (ret != 0) | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 165 | return ret; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 166 |  | 
|  | 167 | /* set power-up and non-reset bits */ | 
| Mark Brown | afad95f | 2012-02-17 12:04:41 -0800 | [diff] [blame] | 168 | ret = snd_soc_update_bits(codec, AK4104_REG_CONTROL1, | 
|  | 169 | AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN, | 
|  | 170 | AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN); | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 171 | if (ret < 0) | 
|  | 172 | return ret; | 
|  | 173 |  | 
|  | 174 | /* enable transmitter */ | 
| Mark Brown | afad95f | 2012-02-17 12:04:41 -0800 | [diff] [blame] | 175 | ret = snd_soc_update_bits(codec, AK4104_REG_TX, | 
|  | 176 | AK4104_TX_TXE, AK4104_TX_TXE); | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 177 | if (ret < 0) | 
|  | 178 | return ret; | 
|  | 179 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 180 | return 0; | 
|  | 181 | } | 
|  | 182 |  | 
|  | 183 | static int ak4104_remove(struct snd_soc_codec *codec) | 
|  | 184 | { | 
| Mark Brown | afad95f | 2012-02-17 12:04:41 -0800 | [diff] [blame] | 185 | snd_soc_update_bits(codec, AK4104_REG_CONTROL1, | 
|  | 186 | AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN, 0); | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 187 |  | 
| Mark Brown | afad95f | 2012-02-17 12:04:41 -0800 | [diff] [blame] | 188 | return 0; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 189 | } | 
|  | 190 |  | 
|  | 191 | static struct snd_soc_codec_driver soc_codec_device_ak4104 = { | 
|  | 192 | .probe =	ak4104_probe, | 
|  | 193 | .remove =	ak4104_remove, | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 194 | }; | 
|  | 195 |  | 
|  | 196 | static const struct regmap_config ak4104_regmap = { | 
|  | 197 | .reg_bits = 8, | 
|  | 198 | .val_bits = 8, | 
|  | 199 |  | 
|  | 200 | .max_register = AK4104_NUM_REGS - 1, | 
|  | 201 | .read_flag_mask = AK4104_READ, | 
|  | 202 | .write_flag_mask = AK4104_WRITE, | 
|  | 203 |  | 
|  | 204 | .cache_type = REGCACHE_RBTREE, | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 205 | }; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 206 |  | 
|  | 207 | static int ak4104_spi_probe(struct spi_device *spi) | 
|  | 208 | { | 
| Daniel Mack | 385a4c2 | 2012-11-14 18:28:39 +0800 | [diff] [blame] | 209 | struct device_node *np = spi->dev.of_node; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 210 | struct ak4104_private *ak4104; | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 211 | unsigned int val; | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 212 | int ret; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 213 |  | 
|  | 214 | spi->bits_per_word = 8; | 
|  | 215 | spi->mode = SPI_MODE_0; | 
|  | 216 | ret = spi_setup(spi); | 
|  | 217 | if (ret < 0) | 
|  | 218 | return ret; | 
|  | 219 |  | 
| Axel Lin | 3922d51 | 2011-12-20 14:37:12 +0800 | [diff] [blame] | 220 | ak4104 = devm_kzalloc(&spi->dev, sizeof(struct ak4104_private), | 
|  | 221 | GFP_KERNEL); | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 222 | if (ak4104 == NULL) | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 223 | return -ENOMEM; | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 224 |  | 
| Tushar Behera | a273cd1 | 2012-11-22 09:38:36 +0530 | [diff] [blame] | 225 | ak4104->regmap = devm_regmap_init_spi(spi, &ak4104_regmap); | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 226 | if (IS_ERR(ak4104->regmap)) { | 
|  | 227 | ret = PTR_ERR(ak4104->regmap); | 
|  | 228 | return ret; | 
|  | 229 | } | 
|  | 230 |  | 
| Daniel Mack | 385a4c2 | 2012-11-14 18:28:39 +0800 | [diff] [blame] | 231 | if (np) { | 
|  | 232 | enum of_gpio_flags flags; | 
|  | 233 | int gpio = of_get_named_gpio_flags(np, "reset-gpio", 0, &flags); | 
|  | 234 |  | 
|  | 235 | if (gpio_is_valid(gpio)) { | 
|  | 236 | ret = devm_gpio_request_one(&spi->dev, gpio, | 
|  | 237 | flags & OF_GPIO_ACTIVE_LOW ? | 
|  | 238 | GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH, | 
|  | 239 | "ak4104 reset"); | 
|  | 240 | if (ret < 0) | 
|  | 241 | return ret; | 
|  | 242 | } | 
|  | 243 | } | 
|  | 244 |  | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 245 | /* read the 'reserved' register - according to the datasheet, it | 
|  | 246 | * should contain 0x5b. Not a good way to verify the presence of | 
|  | 247 | * the device, but there is no hardware ID register. */ | 
|  | 248 | ret = regmap_read(ak4104->regmap, AK4104_REG_RESERVED, &val); | 
|  | 249 | if (ret != 0) | 
| Tushar Behera | a273cd1 | 2012-11-22 09:38:36 +0530 | [diff] [blame] | 250 | return ret; | 
|  | 251 | if (val != AK4104_RESERVED_VAL) | 
|  | 252 | return -ENODEV; | 
| Mark Brown | 2901d6e | 2012-02-17 12:14:18 -0800 | [diff] [blame] | 253 |  | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 254 | spi_set_drvdata(spi, ak4104); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 255 |  | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 256 | ret = snd_soc_register_codec(&spi->dev, | 
|  | 257 | &soc_codec_device_ak4104, &ak4104_dai, 1); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 258 | return ret; | 
|  | 259 | } | 
|  | 260 |  | 
| Bill Pemberton | 7a79e94 | 2012-12-07 09:26:37 -0500 | [diff] [blame] | 261 | static int ak4104_spi_remove(struct spi_device *spi) | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 262 | { | 
| Liam Girdwood | f0fba2a | 2010-03-17 20:15:21 +0000 | [diff] [blame] | 263 | snd_soc_unregister_codec(&spi->dev); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 264 | return 0; | 
|  | 265 | } | 
|  | 266 |  | 
| Daniel Mack | ac5dbea | 2012-10-07 17:51:24 +0200 | [diff] [blame] | 267 | static const struct of_device_id ak4104_of_match[] = { | 
|  | 268 | { .compatible = "asahi-kasei,ak4104", }, | 
|  | 269 | { } | 
|  | 270 | }; | 
|  | 271 | MODULE_DEVICE_TABLE(of, ak4104_of_match); | 
|  | 272 |  | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 273 | static struct spi_driver ak4104_spi_driver = { | 
|  | 274 | .driver  = { | 
|  | 275 | .name   = DRV_NAME, | 
|  | 276 | .owner  = THIS_MODULE, | 
| Daniel Mack | ac5dbea | 2012-10-07 17:51:24 +0200 | [diff] [blame] | 277 | .of_match_table = ak4104_of_match, | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 278 | }, | 
|  | 279 | .probe  = ak4104_spi_probe, | 
| Bill Pemberton | 7a79e94 | 2012-12-07 09:26:37 -0500 | [diff] [blame] | 280 | .remove = ak4104_spi_remove, | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 281 | }; | 
|  | 282 |  | 
| Mark Brown | 38d78ba | 2012-02-16 22:50:35 -0800 | [diff] [blame] | 283 | module_spi_driver(ak4104_spi_driver); | 
| Daniel Mack | a381934 | 2009-03-09 02:13:17 +0100 | [diff] [blame] | 284 |  | 
|  | 285 | MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); | 
|  | 286 | MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver"); | 
|  | 287 | MODULE_LICENSE("GPL"); | 
|  | 288 |  |