18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * MFD driver for wl1273 FM radio and audio codec submodules. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Nokia Corporation 68c2ecf20Sopenharmony_ci * Author: Matti Aaltonen <matti.j.aaltonen@nokia.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/mfd/wl1273-core.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#define DRIVER_DESC "WL1273 FM Radio Core" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistatic const struct i2c_device_id wl1273_driver_id_table[] = { 168c2ecf20Sopenharmony_ci { WL1273_FM_DRIVER_NAME, 0 }, 178c2ecf20Sopenharmony_ci { } 188c2ecf20Sopenharmony_ci}; 198c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, wl1273_driver_id_table); 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci struct i2c_client *client = core->client; 248c2ecf20Sopenharmony_ci u8 b[2]; 258c2ecf20Sopenharmony_ci int r; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci r = i2c_smbus_read_i2c_block_data(client, reg, sizeof(b), b); 288c2ecf20Sopenharmony_ci if (r != 2) { 298c2ecf20Sopenharmony_ci dev_err(&client->dev, "%s: Read: %d fails.\n", __func__, reg); 308c2ecf20Sopenharmony_ci return -EREMOTEIO; 318c2ecf20Sopenharmony_ci } 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci *value = (u16)b[0] << 8 | b[1]; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci return 0; 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct i2c_client *client = core->client; 418c2ecf20Sopenharmony_ci u8 buf[] = { (param >> 8) & 0xff, param & 0xff }; 428c2ecf20Sopenharmony_ci int r; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci r = i2c_smbus_write_i2c_block_data(client, cmd, sizeof(buf), buf); 458c2ecf20Sopenharmony_ci if (r) { 468c2ecf20Sopenharmony_ci dev_err(&client->dev, "%s: Cmd: %d fails.\n", __func__, cmd); 478c2ecf20Sopenharmony_ci return r; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci return 0; 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct i2c_client *client = core->client; 568c2ecf20Sopenharmony_ci struct i2c_msg msg; 578c2ecf20Sopenharmony_ci int r; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci msg.addr = client->addr; 608c2ecf20Sopenharmony_ci msg.flags = 0; 618c2ecf20Sopenharmony_ci msg.buf = data; 628c2ecf20Sopenharmony_ci msg.len = len; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci r = i2c_transfer(client->adapter, &msg, 1); 658c2ecf20Sopenharmony_ci if (r != 1) { 668c2ecf20Sopenharmony_ci dev_err(&client->dev, "%s: write error.\n", __func__); 678c2ecf20Sopenharmony_ci return -EREMOTEIO; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return 0; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/** 748c2ecf20Sopenharmony_ci * wl1273_fm_set_audio() - Set audio mode. 758c2ecf20Sopenharmony_ci * @core: A pointer to the device struct. 768c2ecf20Sopenharmony_ci * @new_mode: The new audio mode. 778c2ecf20Sopenharmony_ci * 788c2ecf20Sopenharmony_ci * Audio modes are WL1273_AUDIO_DIGITAL and WL1273_AUDIO_ANALOG. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_cistatic int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int new_mode) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci int r = 0; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (core->mode == WL1273_MODE_OFF || 858c2ecf20Sopenharmony_ci core->mode == WL1273_MODE_SUSPENDED) 868c2ecf20Sopenharmony_ci return -EPERM; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci if (core->mode == WL1273_MODE_RX && new_mode == WL1273_AUDIO_DIGITAL) { 898c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_PCM_MODE_SET, 908c2ecf20Sopenharmony_ci WL1273_PCM_DEF_MODE); 918c2ecf20Sopenharmony_ci if (r) 928c2ecf20Sopenharmony_ci goto out; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, 958c2ecf20Sopenharmony_ci core->i2s_mode); 968c2ecf20Sopenharmony_ci if (r) 978c2ecf20Sopenharmony_ci goto out; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, 1008c2ecf20Sopenharmony_ci WL1273_AUDIO_ENABLE_I2S); 1018c2ecf20Sopenharmony_ci if (r) 1028c2ecf20Sopenharmony_ci goto out; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci } else if (core->mode == WL1273_MODE_RX && 1058c2ecf20Sopenharmony_ci new_mode == WL1273_AUDIO_ANALOG) { 1068c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE, 1078c2ecf20Sopenharmony_ci WL1273_AUDIO_ENABLE_ANALOG); 1088c2ecf20Sopenharmony_ci if (r) 1098c2ecf20Sopenharmony_ci goto out; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci } else if (core->mode == WL1273_MODE_TX && 1128c2ecf20Sopenharmony_ci new_mode == WL1273_AUDIO_DIGITAL) { 1138c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET, 1148c2ecf20Sopenharmony_ci core->i2s_mode); 1158c2ecf20Sopenharmony_ci if (r) 1168c2ecf20Sopenharmony_ci goto out; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, 1198c2ecf20Sopenharmony_ci WL1273_AUDIO_IO_SET_I2S); 1208c2ecf20Sopenharmony_ci if (r) 1218c2ecf20Sopenharmony_ci goto out; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci } else if (core->mode == WL1273_MODE_TX && 1248c2ecf20Sopenharmony_ci new_mode == WL1273_AUDIO_ANALOG) { 1258c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET, 1268c2ecf20Sopenharmony_ci WL1273_AUDIO_IO_SET_ANALOG); 1278c2ecf20Sopenharmony_ci if (r) 1288c2ecf20Sopenharmony_ci goto out; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci core->audio_mode = new_mode; 1328c2ecf20Sopenharmony_ciout: 1338c2ecf20Sopenharmony_ci return r; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci/** 1378c2ecf20Sopenharmony_ci * wl1273_fm_set_volume() - Set volume. 1388c2ecf20Sopenharmony_ci * @core: A pointer to the device struct. 1398c2ecf20Sopenharmony_ci * @volume: The new volume value. 1408c2ecf20Sopenharmony_ci */ 1418c2ecf20Sopenharmony_cistatic int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci int r; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (volume > WL1273_MAX_VOLUME) 1468c2ecf20Sopenharmony_ci return -EINVAL; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci if (core->volume == volume) 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci r = wl1273_fm_write_cmd(core, WL1273_VOLUME_SET, volume); 1528c2ecf20Sopenharmony_ci if (r) 1538c2ecf20Sopenharmony_ci return r; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci core->volume = volume; 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int wl1273_core_probe(struct i2c_client *client, 1608c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct wl1273_fm_platform_data *pdata = dev_get_platdata(&client->dev); 1638c2ecf20Sopenharmony_ci struct wl1273_core *core; 1648c2ecf20Sopenharmony_ci struct mfd_cell *cell; 1658c2ecf20Sopenharmony_ci int children = 0; 1668c2ecf20Sopenharmony_ci int r = 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "%s\n", __func__); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (!pdata) { 1718c2ecf20Sopenharmony_ci dev_err(&client->dev, "No platform data.\n"); 1728c2ecf20Sopenharmony_ci return -EINVAL; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci if (!(pdata->children & WL1273_RADIO_CHILD)) { 1768c2ecf20Sopenharmony_ci dev_err(&client->dev, "Cannot function without radio child.\n"); 1778c2ecf20Sopenharmony_ci return -EINVAL; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL); 1818c2ecf20Sopenharmony_ci if (!core) 1828c2ecf20Sopenharmony_ci return -ENOMEM; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci core->pdata = pdata; 1858c2ecf20Sopenharmony_ci core->client = client; 1868c2ecf20Sopenharmony_ci mutex_init(&core->lock); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci i2c_set_clientdata(client, core); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "%s: Have V4L2.\n", __func__); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci cell = &core->cells[children]; 1938c2ecf20Sopenharmony_ci cell->name = "wl1273_fm_radio"; 1948c2ecf20Sopenharmony_ci cell->platform_data = &core; 1958c2ecf20Sopenharmony_ci cell->pdata_size = sizeof(core); 1968c2ecf20Sopenharmony_ci children++; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci core->read = wl1273_fm_read_reg; 1998c2ecf20Sopenharmony_ci core->write = wl1273_fm_write_cmd; 2008c2ecf20Sopenharmony_ci core->write_data = wl1273_fm_write_data; 2018c2ecf20Sopenharmony_ci core->set_audio = wl1273_fm_set_audio; 2028c2ecf20Sopenharmony_ci core->set_volume = wl1273_fm_set_volume; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (pdata->children & WL1273_CODEC_CHILD) { 2058c2ecf20Sopenharmony_ci cell = &core->cells[children]; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "%s: Have codec.\n", __func__); 2088c2ecf20Sopenharmony_ci cell->name = "wl1273-codec"; 2098c2ecf20Sopenharmony_ci cell->platform_data = &core; 2108c2ecf20Sopenharmony_ci cell->pdata_size = sizeof(core); 2118c2ecf20Sopenharmony_ci children++; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "%s: number of children: %d.\n", 2158c2ecf20Sopenharmony_ci __func__, children); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci r = devm_mfd_add_devices(&client->dev, -1, core->cells, 2188c2ecf20Sopenharmony_ci children, NULL, 0, NULL); 2198c2ecf20Sopenharmony_ci if (r) 2208c2ecf20Sopenharmony_ci goto err; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return 0; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cierr: 2258c2ecf20Sopenharmony_ci pdata->free_resources(); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci dev_dbg(&client->dev, "%s\n", __func__); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return r; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic struct i2c_driver wl1273_core_driver = { 2338c2ecf20Sopenharmony_ci .driver = { 2348c2ecf20Sopenharmony_ci .name = WL1273_FM_DRIVER_NAME, 2358c2ecf20Sopenharmony_ci }, 2368c2ecf20Sopenharmony_ci .probe = wl1273_core_probe, 2378c2ecf20Sopenharmony_ci .id_table = wl1273_driver_id_table, 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cistatic int __init wl1273_core_init(void) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci int r; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci r = i2c_add_driver(&wl1273_core_driver); 2458c2ecf20Sopenharmony_ci if (r) { 2468c2ecf20Sopenharmony_ci pr_err(WL1273_FM_DRIVER_NAME 2478c2ecf20Sopenharmony_ci ": driver registration failed\n"); 2488c2ecf20Sopenharmony_ci return r; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return r; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic void __exit wl1273_core_exit(void) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci i2c_del_driver(&wl1273_core_driver); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_cilate_initcall(wl1273_core_init); 2598c2ecf20Sopenharmony_cimodule_exit(wl1273_core_exit); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>"); 2628c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 2638c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 264