twl4030-audio.c (6840B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * MFD driver for twl4030 audio submodule, which contains an audio codec, and 4 * the vibra control. 5 * 6 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> 7 * 8 * Copyright: (C) 2009 Nokia Corporation 9 */ 10 11#include <linux/module.h> 12#include <linux/types.h> 13#include <linux/slab.h> 14#include <linux/kernel.h> 15#include <linux/fs.h> 16#include <linux/platform_device.h> 17#include <linux/of.h> 18#include <linux/of_platform.h> 19#include <linux/mfd/twl.h> 20#include <linux/mfd/core.h> 21#include <linux/mfd/twl4030-audio.h> 22 23#define TWL4030_AUDIO_CELLS 2 24 25static struct platform_device *twl4030_audio_dev; 26 27struct twl4030_audio_resource { 28 int request_count; 29 u8 reg; 30 u8 mask; 31}; 32 33struct twl4030_audio { 34 unsigned int audio_mclk; 35 struct mutex mutex; 36 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; 37 struct mfd_cell cells[TWL4030_AUDIO_CELLS]; 38}; 39 40/* 41 * Modify the resource, the function returns the content of the register 42 * after the modification. 43 */ 44static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) 45{ 46 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 47 u8 val; 48 49 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 50 audio->resource[id].reg); 51 52 if (enable) 53 val |= audio->resource[id].mask; 54 else 55 val &= ~audio->resource[id].mask; 56 57 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 58 val, audio->resource[id].reg); 59 60 return val; 61} 62 63static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) 64{ 65 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 66 u8 val; 67 68 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 69 audio->resource[id].reg); 70 71 return val; 72} 73 74/* 75 * Enable the resource. 76 * The function returns with error or the content of the register 77 */ 78int twl4030_audio_enable_resource(enum twl4030_audio_res id) 79{ 80 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 81 int val; 82 83 if (id >= TWL4030_AUDIO_RES_MAX) { 84 dev_err(&twl4030_audio_dev->dev, 85 "Invalid resource ID (%u)\n", id); 86 return -EINVAL; 87 } 88 89 mutex_lock(&audio->mutex); 90 if (!audio->resource[id].request_count) 91 /* Resource was disabled, enable it */ 92 val = twl4030_audio_set_resource(id, 1); 93 else 94 val = twl4030_audio_get_resource(id); 95 96 audio->resource[id].request_count++; 97 mutex_unlock(&audio->mutex); 98 99 return val; 100} 101EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); 102 103/* 104 * Disable the resource. 105 * The function returns with error or the content of the register 106 */ 107int twl4030_audio_disable_resource(enum twl4030_audio_res id) 108{ 109 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 110 int val; 111 112 if (id >= TWL4030_AUDIO_RES_MAX) { 113 dev_err(&twl4030_audio_dev->dev, 114 "Invalid resource ID (%u)\n", id); 115 return -EINVAL; 116 } 117 118 mutex_lock(&audio->mutex); 119 if (!audio->resource[id].request_count) { 120 dev_err(&twl4030_audio_dev->dev, 121 "Resource has been disabled already (%u)\n", id); 122 mutex_unlock(&audio->mutex); 123 return -EPERM; 124 } 125 audio->resource[id].request_count--; 126 127 if (!audio->resource[id].request_count) 128 /* Resource can be disabled now */ 129 val = twl4030_audio_set_resource(id, 0); 130 else 131 val = twl4030_audio_get_resource(id); 132 133 mutex_unlock(&audio->mutex); 134 135 return val; 136} 137EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); 138 139unsigned int twl4030_audio_get_mclk(void) 140{ 141 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); 142 143 return audio->audio_mclk; 144} 145EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); 146 147static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, 148 struct device_node *parent) 149{ 150 struct device_node *node; 151 152 if (pdata && pdata->codec) 153 return true; 154 155 node = of_get_child_by_name(parent, "codec"); 156 if (node) { 157 of_node_put(node); 158 return true; 159 } 160 161 return false; 162} 163 164static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, 165 struct device_node *node) 166{ 167 int vibra; 168 169 if (pdata && pdata->vibra) 170 return true; 171 172 if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) 173 return true; 174 175 return false; 176} 177 178static int twl4030_audio_probe(struct platform_device *pdev) 179{ 180 struct twl4030_audio *audio; 181 struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev); 182 struct device_node *node = pdev->dev.of_node; 183 struct mfd_cell *cell = NULL; 184 int ret, childs = 0; 185 u8 val; 186 187 if (!pdata && !node) { 188 dev_err(&pdev->dev, "Platform data is missing\n"); 189 return -EINVAL; 190 } 191 192 audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), 193 GFP_KERNEL); 194 if (!audio) 195 return -ENOMEM; 196 197 mutex_init(&audio->mutex); 198 audio->audio_mclk = twl_get_hfclk_rate(); 199 200 /* Configure APLL_INFREQ and disable APLL if enabled */ 201 switch (audio->audio_mclk) { 202 case 19200000: 203 val = TWL4030_APLL_INFREQ_19200KHZ; 204 break; 205 case 26000000: 206 val = TWL4030_APLL_INFREQ_26000KHZ; 207 break; 208 case 38400000: 209 val = TWL4030_APLL_INFREQ_38400KHZ; 210 break; 211 default: 212 dev_err(&pdev->dev, "Invalid audio_mclk\n"); 213 return -EINVAL; 214 } 215 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL); 216 217 /* Codec power */ 218 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 219 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; 220 221 /* PLL */ 222 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; 223 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; 224 225 if (twl4030_audio_has_codec(pdata, node)) { 226 cell = &audio->cells[childs]; 227 cell->name = "twl4030-codec"; 228 if (pdata) { 229 cell->platform_data = pdata->codec; 230 cell->pdata_size = sizeof(*pdata->codec); 231 } 232 childs++; 233 } 234 if (twl4030_audio_has_vibra(pdata, node)) { 235 cell = &audio->cells[childs]; 236 cell->name = "twl4030-vibra"; 237 if (pdata) { 238 cell->platform_data = pdata->vibra; 239 cell->pdata_size = sizeof(*pdata->vibra); 240 } 241 childs++; 242 } 243 244 platform_set_drvdata(pdev, audio); 245 twl4030_audio_dev = pdev; 246 247 if (childs) 248 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, 249 childs, NULL, 0, NULL); 250 else { 251 dev_err(&pdev->dev, "No platform data found for childs\n"); 252 ret = -ENODEV; 253 } 254 255 if (ret) 256 twl4030_audio_dev = NULL; 257 258 return ret; 259} 260 261static int twl4030_audio_remove(struct platform_device *pdev) 262{ 263 mfd_remove_devices(&pdev->dev); 264 twl4030_audio_dev = NULL; 265 266 return 0; 267} 268 269static const struct of_device_id twl4030_audio_of_match[] = { 270 {.compatible = "ti,twl4030-audio", }, 271 { }, 272}; 273MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); 274 275static struct platform_driver twl4030_audio_driver = { 276 .driver = { 277 .name = "twl4030-audio", 278 .of_match_table = twl4030_audio_of_match, 279 }, 280 .probe = twl4030_audio_probe, 281 .remove = twl4030_audio_remove, 282}; 283 284module_platform_driver(twl4030_audio_driver); 285 286MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); 287MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); 288MODULE_LICENSE("GPL"); 289MODULE_ALIAS("platform:twl4030-audio");