1/* 2 * MFD driver for twl4030 codec submodule 3 * 4 * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com> 5 * 6 * Copyright: (C) 2009 Nokia Corporation 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 version 2 as 10 * published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA 21 * 22 */ 23 24#include <linux/module.h> 25#include <linux/types.h> 26#include <linux/slab.h> 27#include <linux/kernel.h> 28#include <linux/fs.h> 29#include <linux/platform_device.h> 30#include <linux/i2c/twl.h> 31#include <linux/mfd/core.h> 32#include <linux/mfd/twl4030-codec.h> 33 34#define TWL4030_CODEC_CELLS 2 35 36static struct platform_device *twl4030_codec_dev; 37 38struct twl4030_codec_resource { 39 int request_count; 40 u8 reg; 41 u8 mask; 42}; 43 44struct twl4030_codec { 45 unsigned int audio_mclk; 46 struct mutex mutex; 47 struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX]; 48 struct mfd_cell cells[TWL4030_CODEC_CELLS]; 49}; 50 51/* 52 * Modify the resource, the function returns the content of the register 53 * after the modification. 54 */ 55static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable) 56{ 57 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); 58 u8 val; 59 60 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 61 codec->resource[id].reg); 62 63 if (enable) 64 val |= codec->resource[id].mask; 65 else 66 val &= ~codec->resource[id].mask; 67 68 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 69 val, codec->resource[id].reg); 70 71 return val; 72} 73 74static inline int twl4030_codec_get_resource(enum twl4030_codec_res id) 75{ 76 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); 77 u8 val; 78 79 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, 80 codec->resource[id].reg); 81 82 return val; 83} 84 85/* 86 * Enable the resource. 87 * The function returns with error or the content of the register 88 */ 89int twl4030_codec_enable_resource(enum twl4030_codec_res id) 90{ 91 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); 92 int val; 93 94 if (id >= TWL4030_CODEC_RES_MAX) { 95 dev_err(&twl4030_codec_dev->dev, 96 "Invalid resource ID (%u)\n", id); 97 return -EINVAL; 98 } 99 100 mutex_lock(&codec->mutex); 101 if (!codec->resource[id].request_count) 102 /* Resource was disabled, enable it */ 103 val = twl4030_codec_set_resource(id, 1); 104 else 105 val = twl4030_codec_get_resource(id); 106 107 codec->resource[id].request_count++; 108 mutex_unlock(&codec->mutex); 109 110 return val; 111} 112EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource); 113 114/* 115 * Disable the resource. 116 * The function returns with error or the content of the register 117 */ 118int twl4030_codec_disable_resource(unsigned id) 119{ 120 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); 121 int val; 122 123 if (id >= TWL4030_CODEC_RES_MAX) { 124 dev_err(&twl4030_codec_dev->dev, 125 "Invalid resource ID (%u)\n", id); 126 return -EINVAL; 127 } 128 129 mutex_lock(&codec->mutex); 130 if (!codec->resource[id].request_count) { 131 dev_err(&twl4030_codec_dev->dev, 132 "Resource has been disabled already (%u)\n", id); 133 mutex_unlock(&codec->mutex); 134 return -EPERM; 135 } 136 codec->resource[id].request_count--; 137 138 if (!codec->resource[id].request_count) 139 /* Resource can be disabled now */ 140 val = twl4030_codec_set_resource(id, 0); 141 else 142 val = twl4030_codec_get_resource(id); 143 144 mutex_unlock(&codec->mutex); 145 146 return val; 147} 148EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource); 149 150unsigned int twl4030_codec_get_mclk(void) 151{ 152 struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); 153 154 return codec->audio_mclk; 155} 156EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk); 157 158static int __devinit twl4030_codec_probe(struct platform_device *pdev) 159{ 160 struct twl4030_codec *codec; 161 struct twl4030_codec_data *pdata = pdev->dev.platform_data; 162 struct mfd_cell *cell = NULL; 163 int ret, childs = 0; 164 u8 val; 165 166 if (!pdata) { 167 dev_err(&pdev->dev, "Platform data is missing\n"); 168 return -EINVAL; 169 } 170 171 /* Configure APLL_INFREQ and disable APLL if enabled */ 172 val = 0; 173 switch (pdata->audio_mclk) { 174 case 19200000: 175 val |= TWL4030_APLL_INFREQ_19200KHZ; 176 break; 177 case 26000000: 178 val |= TWL4030_APLL_INFREQ_26000KHZ; 179 break; 180 case 38400000: 181 val |= TWL4030_APLL_INFREQ_38400KHZ; 182 break; 183 default: 184 dev_err(&pdev->dev, "Invalid audio_mclk\n"); 185 return -EINVAL; 186 } 187 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, 188 val, TWL4030_REG_APLL_CTL); 189 190 codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL); 191 if (!codec) 192 return -ENOMEM; 193 194 platform_set_drvdata(pdev, codec); 195 196 twl4030_codec_dev = pdev; 197 mutex_init(&codec->mutex); 198 codec->audio_mclk = pdata->audio_mclk; 199 200 /* Codec power */ 201 codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE; 202 codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ; 203 204 /* PLL */ 205 codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL; 206 codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN; 207 208 if (pdata->audio) { 209 cell = &codec->cells[childs]; 210 cell->name = "twl4030_codec_audio"; 211 cell->platform_data = pdata->audio; 212 cell->data_size = sizeof(*pdata->audio); 213 childs++; 214 } 215 if (pdata->vibra) { 216 cell = &codec->cells[childs]; 217 cell->name = "twl4030_codec_vibra"; 218 cell->platform_data = pdata->vibra; 219 cell->data_size = sizeof(*pdata->vibra); 220 childs++; 221 } 222 223 if (childs) 224 ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells, 225 childs, NULL, 0); 226 else { 227 dev_err(&pdev->dev, "No platform data found for childs\n"); 228 ret = -ENODEV; 229 } 230 231 if (!ret) 232 return 0; 233 234 platform_set_drvdata(pdev, NULL); 235 kfree(codec); 236 twl4030_codec_dev = NULL; 237 return ret; 238} 239 240static int __devexit twl4030_codec_remove(struct platform_device *pdev) 241{ 242 struct twl4030_codec *codec = platform_get_drvdata(pdev); 243 244 mfd_remove_devices(&pdev->dev); 245 platform_set_drvdata(pdev, NULL); 246 kfree(codec); 247 twl4030_codec_dev = NULL; 248 249 return 0; 250} 251 252MODULE_ALIAS("platform:twl4030_codec"); 253 254static struct platform_driver twl4030_codec_driver = { 255 .probe = twl4030_codec_probe, 256 .remove = __devexit_p(twl4030_codec_remove), 257 .driver = { 258 .owner = THIS_MODULE, 259 .name = "twl4030_codec", 260 }, 261}; 262 263static int __devinit twl4030_codec_init(void) 264{ 265 return platform_driver_register(&twl4030_codec_driver); 266} 267module_init(twl4030_codec_init); 268 269static void __devexit twl4030_codec_exit(void) 270{ 271 platform_driver_unregister(&twl4030_codec_driver); 272} 273module_exit(twl4030_codec_exit); 274 275MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>"); 276MODULE_LICENSE("GPL"); 277