12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2383268a8SMatti Aaltonen /*
3383268a8SMatti Aaltonen * MFD driver for wl1273 FM radio and audio codec submodules.
4383268a8SMatti Aaltonen *
594fd5b74SMatti Aaltonen * Copyright (C) 2011 Nokia Corporation
6383268a8SMatti Aaltonen * Author: Matti Aaltonen <matti.j.aaltonen@nokia.com>
7383268a8SMatti Aaltonen */
8383268a8SMatti Aaltonen
9383268a8SMatti Aaltonen #include <linux/mfd/wl1273-core.h>
10383268a8SMatti Aaltonen #include <linux/slab.h>
114e36dd33SPaul Gortmaker #include <linux/module.h>
12383268a8SMatti Aaltonen
13383268a8SMatti Aaltonen #define DRIVER_DESC "WL1273 FM Radio Core"
14383268a8SMatti Aaltonen
151206552bSAxel Lin static const struct i2c_device_id wl1273_driver_id_table[] = {
16383268a8SMatti Aaltonen { WL1273_FM_DRIVER_NAME, 0 },
17383268a8SMatti Aaltonen { }
18383268a8SMatti Aaltonen };
19383268a8SMatti Aaltonen MODULE_DEVICE_TABLE(i2c, wl1273_driver_id_table);
20383268a8SMatti Aaltonen
wl1273_fm_read_reg(struct wl1273_core * core,u8 reg,u16 * value)2194fd5b74SMatti Aaltonen static int wl1273_fm_read_reg(struct wl1273_core *core, u8 reg, u16 *value)
2294fd5b74SMatti Aaltonen {
2394fd5b74SMatti Aaltonen struct i2c_client *client = core->client;
2494fd5b74SMatti Aaltonen u8 b[2];
2594fd5b74SMatti Aaltonen int r;
2694fd5b74SMatti Aaltonen
2794fd5b74SMatti Aaltonen r = i2c_smbus_read_i2c_block_data(client, reg, sizeof(b), b);
2894fd5b74SMatti Aaltonen if (r != 2) {
2994fd5b74SMatti Aaltonen dev_err(&client->dev, "%s: Read: %d fails.\n", __func__, reg);
3094fd5b74SMatti Aaltonen return -EREMOTEIO;
3194fd5b74SMatti Aaltonen }
3294fd5b74SMatti Aaltonen
3394fd5b74SMatti Aaltonen *value = (u16)b[0] << 8 | b[1];
3494fd5b74SMatti Aaltonen
3594fd5b74SMatti Aaltonen return 0;
3694fd5b74SMatti Aaltonen }
3794fd5b74SMatti Aaltonen
wl1273_fm_write_cmd(struct wl1273_core * core,u8 cmd,u16 param)3894fd5b74SMatti Aaltonen static int wl1273_fm_write_cmd(struct wl1273_core *core, u8 cmd, u16 param)
3994fd5b74SMatti Aaltonen {
4094fd5b74SMatti Aaltonen struct i2c_client *client = core->client;
4194fd5b74SMatti Aaltonen u8 buf[] = { (param >> 8) & 0xff, param & 0xff };
4294fd5b74SMatti Aaltonen int r;
4394fd5b74SMatti Aaltonen
4494fd5b74SMatti Aaltonen r = i2c_smbus_write_i2c_block_data(client, cmd, sizeof(buf), buf);
4594fd5b74SMatti Aaltonen if (r) {
4694fd5b74SMatti Aaltonen dev_err(&client->dev, "%s: Cmd: %d fails.\n", __func__, cmd);
4794fd5b74SMatti Aaltonen return r;
4894fd5b74SMatti Aaltonen }
4994fd5b74SMatti Aaltonen
5094fd5b74SMatti Aaltonen return 0;
5194fd5b74SMatti Aaltonen }
5294fd5b74SMatti Aaltonen
wl1273_fm_write_data(struct wl1273_core * core,u8 * data,u16 len)5394fd5b74SMatti Aaltonen static int wl1273_fm_write_data(struct wl1273_core *core, u8 *data, u16 len)
5494fd5b74SMatti Aaltonen {
5594fd5b74SMatti Aaltonen struct i2c_client *client = core->client;
5694fd5b74SMatti Aaltonen struct i2c_msg msg;
5794fd5b74SMatti Aaltonen int r;
5894fd5b74SMatti Aaltonen
5994fd5b74SMatti Aaltonen msg.addr = client->addr;
6094fd5b74SMatti Aaltonen msg.flags = 0;
6194fd5b74SMatti Aaltonen msg.buf = data;
6294fd5b74SMatti Aaltonen msg.len = len;
6394fd5b74SMatti Aaltonen
6494fd5b74SMatti Aaltonen r = i2c_transfer(client->adapter, &msg, 1);
6594fd5b74SMatti Aaltonen if (r != 1) {
6694fd5b74SMatti Aaltonen dev_err(&client->dev, "%s: write error.\n", __func__);
6794fd5b74SMatti Aaltonen return -EREMOTEIO;
6894fd5b74SMatti Aaltonen }
6994fd5b74SMatti Aaltonen
7094fd5b74SMatti Aaltonen return 0;
7194fd5b74SMatti Aaltonen }
7294fd5b74SMatti Aaltonen
7394fd5b74SMatti Aaltonen /**
7494fd5b74SMatti Aaltonen * wl1273_fm_set_audio() - Set audio mode.
7594fd5b74SMatti Aaltonen * @core: A pointer to the device struct.
7694fd5b74SMatti Aaltonen * @new_mode: The new audio mode.
7794fd5b74SMatti Aaltonen *
7894fd5b74SMatti Aaltonen * Audio modes are WL1273_AUDIO_DIGITAL and WL1273_AUDIO_ANALOG.
7994fd5b74SMatti Aaltonen */
wl1273_fm_set_audio(struct wl1273_core * core,unsigned int new_mode)8094fd5b74SMatti Aaltonen static int wl1273_fm_set_audio(struct wl1273_core *core, unsigned int new_mode)
8194fd5b74SMatti Aaltonen {
8294fd5b74SMatti Aaltonen int r = 0;
8394fd5b74SMatti Aaltonen
8494fd5b74SMatti Aaltonen if (core->mode == WL1273_MODE_OFF ||
8594fd5b74SMatti Aaltonen core->mode == WL1273_MODE_SUSPENDED)
8694fd5b74SMatti Aaltonen return -EPERM;
8794fd5b74SMatti Aaltonen
8894fd5b74SMatti Aaltonen if (core->mode == WL1273_MODE_RX && new_mode == WL1273_AUDIO_DIGITAL) {
8994fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_PCM_MODE_SET,
9094fd5b74SMatti Aaltonen WL1273_PCM_DEF_MODE);
9194fd5b74SMatti Aaltonen if (r)
9294fd5b74SMatti Aaltonen goto out;
9394fd5b74SMatti Aaltonen
9494fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET,
9594fd5b74SMatti Aaltonen core->i2s_mode);
9694fd5b74SMatti Aaltonen if (r)
9794fd5b74SMatti Aaltonen goto out;
9894fd5b74SMatti Aaltonen
9994fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
10094fd5b74SMatti Aaltonen WL1273_AUDIO_ENABLE_I2S);
10194fd5b74SMatti Aaltonen if (r)
10294fd5b74SMatti Aaltonen goto out;
10394fd5b74SMatti Aaltonen
10494fd5b74SMatti Aaltonen } else if (core->mode == WL1273_MODE_RX &&
10594fd5b74SMatti Aaltonen new_mode == WL1273_AUDIO_ANALOG) {
10694fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_AUDIO_ENABLE,
10794fd5b74SMatti Aaltonen WL1273_AUDIO_ENABLE_ANALOG);
10894fd5b74SMatti Aaltonen if (r)
10994fd5b74SMatti Aaltonen goto out;
11094fd5b74SMatti Aaltonen
11194fd5b74SMatti Aaltonen } else if (core->mode == WL1273_MODE_TX &&
11294fd5b74SMatti Aaltonen new_mode == WL1273_AUDIO_DIGITAL) {
11394fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_I2S_MODE_CONFIG_SET,
11494fd5b74SMatti Aaltonen core->i2s_mode);
11594fd5b74SMatti Aaltonen if (r)
11694fd5b74SMatti Aaltonen goto out;
11794fd5b74SMatti Aaltonen
11894fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET,
11994fd5b74SMatti Aaltonen WL1273_AUDIO_IO_SET_I2S);
12094fd5b74SMatti Aaltonen if (r)
12194fd5b74SMatti Aaltonen goto out;
12294fd5b74SMatti Aaltonen
12394fd5b74SMatti Aaltonen } else if (core->mode == WL1273_MODE_TX &&
12494fd5b74SMatti Aaltonen new_mode == WL1273_AUDIO_ANALOG) {
12594fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_AUDIO_IO_SET,
12694fd5b74SMatti Aaltonen WL1273_AUDIO_IO_SET_ANALOG);
12794fd5b74SMatti Aaltonen if (r)
12894fd5b74SMatti Aaltonen goto out;
12994fd5b74SMatti Aaltonen }
13094fd5b74SMatti Aaltonen
13194fd5b74SMatti Aaltonen core->audio_mode = new_mode;
13294fd5b74SMatti Aaltonen out:
13394fd5b74SMatti Aaltonen return r;
13494fd5b74SMatti Aaltonen }
13594fd5b74SMatti Aaltonen
13694fd5b74SMatti Aaltonen /**
13794fd5b74SMatti Aaltonen * wl1273_fm_set_volume() - Set volume.
13894fd5b74SMatti Aaltonen * @core: A pointer to the device struct.
13994fd5b74SMatti Aaltonen * @volume: The new volume value.
14094fd5b74SMatti Aaltonen */
wl1273_fm_set_volume(struct wl1273_core * core,unsigned int volume)14194fd5b74SMatti Aaltonen static int wl1273_fm_set_volume(struct wl1273_core *core, unsigned int volume)
14294fd5b74SMatti Aaltonen {
14394fd5b74SMatti Aaltonen int r;
14494fd5b74SMatti Aaltonen
14594fd5b74SMatti Aaltonen if (volume > WL1273_MAX_VOLUME)
14694fd5b74SMatti Aaltonen return -EINVAL;
14794fd5b74SMatti Aaltonen
14894fd5b74SMatti Aaltonen if (core->volume == volume)
14994fd5b74SMatti Aaltonen return 0;
15094fd5b74SMatti Aaltonen
15194fd5b74SMatti Aaltonen r = wl1273_fm_write_cmd(core, WL1273_VOLUME_SET, volume);
15294fd5b74SMatti Aaltonen if (r)
15394fd5b74SMatti Aaltonen return r;
15494fd5b74SMatti Aaltonen
15594fd5b74SMatti Aaltonen core->volume = volume;
15694fd5b74SMatti Aaltonen return 0;
15794fd5b74SMatti Aaltonen }
15894fd5b74SMatti Aaltonen
wl1273_core_probe(struct i2c_client * client)159490e11b7SUwe Kleine-König static int wl1273_core_probe(struct i2c_client *client)
160383268a8SMatti Aaltonen {
161334a41ceSJingoo Han struct wl1273_fm_platform_data *pdata = dev_get_platdata(&client->dev);
162383268a8SMatti Aaltonen struct wl1273_core *core;
163383268a8SMatti Aaltonen struct mfd_cell *cell;
164383268a8SMatti Aaltonen int children = 0;
165383268a8SMatti Aaltonen int r = 0;
166383268a8SMatti Aaltonen
167383268a8SMatti Aaltonen dev_dbg(&client->dev, "%s\n", __func__);
168383268a8SMatti Aaltonen
169383268a8SMatti Aaltonen if (!pdata) {
170383268a8SMatti Aaltonen dev_err(&client->dev, "No platform data.\n");
171383268a8SMatti Aaltonen return -EINVAL;
172383268a8SMatti Aaltonen }
173383268a8SMatti Aaltonen
174383268a8SMatti Aaltonen if (!(pdata->children & WL1273_RADIO_CHILD)) {
175383268a8SMatti Aaltonen dev_err(&client->dev, "Cannot function without radio child.\n");
176383268a8SMatti Aaltonen return -EINVAL;
177383268a8SMatti Aaltonen }
178383268a8SMatti Aaltonen
179bba07827SJingoo Han core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL);
180383268a8SMatti Aaltonen if (!core)
181383268a8SMatti Aaltonen return -ENOMEM;
182383268a8SMatti Aaltonen
183383268a8SMatti Aaltonen core->pdata = pdata;
184383268a8SMatti Aaltonen core->client = client;
185383268a8SMatti Aaltonen mutex_init(&core->lock);
186383268a8SMatti Aaltonen
187383268a8SMatti Aaltonen i2c_set_clientdata(client, core);
188383268a8SMatti Aaltonen
189383268a8SMatti Aaltonen dev_dbg(&client->dev, "%s: Have V4L2.\n", __func__);
190383268a8SMatti Aaltonen
191383268a8SMatti Aaltonen cell = &core->cells[children];
192383268a8SMatti Aaltonen cell->name = "wl1273_fm_radio";
1939e554696SSamuel Ortiz cell->platform_data = &core;
1949e554696SSamuel Ortiz cell->pdata_size = sizeof(core);
195383268a8SMatti Aaltonen children++;
196383268a8SMatti Aaltonen
19794fd5b74SMatti Aaltonen core->read = wl1273_fm_read_reg;
19894fd5b74SMatti Aaltonen core->write = wl1273_fm_write_cmd;
19994fd5b74SMatti Aaltonen core->write_data = wl1273_fm_write_data;
20094fd5b74SMatti Aaltonen core->set_audio = wl1273_fm_set_audio;
20194fd5b74SMatti Aaltonen core->set_volume = wl1273_fm_set_volume;
20294fd5b74SMatti Aaltonen
203383268a8SMatti Aaltonen if (pdata->children & WL1273_CODEC_CHILD) {
204383268a8SMatti Aaltonen cell = &core->cells[children];
205383268a8SMatti Aaltonen
206383268a8SMatti Aaltonen dev_dbg(&client->dev, "%s: Have codec.\n", __func__);
207383268a8SMatti Aaltonen cell->name = "wl1273-codec";
2089e554696SSamuel Ortiz cell->platform_data = &core;
2099e554696SSamuel Ortiz cell->pdata_size = sizeof(core);
210383268a8SMatti Aaltonen children++;
211383268a8SMatti Aaltonen }
212383268a8SMatti Aaltonen
213383268a8SMatti Aaltonen dev_dbg(&client->dev, "%s: number of children: %d.\n",
214383268a8SMatti Aaltonen __func__, children);
215383268a8SMatti Aaltonen
21614856f75SLaxman Dewangan r = devm_mfd_add_devices(&client->dev, -1, core->cells,
2170848c94fSMark Brown children, NULL, 0, NULL);
218383268a8SMatti Aaltonen if (r)
219383268a8SMatti Aaltonen goto err;
220383268a8SMatti Aaltonen
221383268a8SMatti Aaltonen return 0;
222383268a8SMatti Aaltonen
223383268a8SMatti Aaltonen err:
224383268a8SMatti Aaltonen pdata->free_resources();
225383268a8SMatti Aaltonen
226383268a8SMatti Aaltonen dev_dbg(&client->dev, "%s\n", __func__);
227383268a8SMatti Aaltonen
228383268a8SMatti Aaltonen return r;
229383268a8SMatti Aaltonen }
230383268a8SMatti Aaltonen
231383268a8SMatti Aaltonen static struct i2c_driver wl1273_core_driver = {
232383268a8SMatti Aaltonen .driver = {
233383268a8SMatti Aaltonen .name = WL1273_FM_DRIVER_NAME,
234383268a8SMatti Aaltonen },
235*9816d859SUwe Kleine-König .probe = wl1273_core_probe,
236383268a8SMatti Aaltonen .id_table = wl1273_driver_id_table,
237383268a8SMatti Aaltonen };
238383268a8SMatti Aaltonen
wl1273_core_init(void)239383268a8SMatti Aaltonen static int __init wl1273_core_init(void)
240383268a8SMatti Aaltonen {
241383268a8SMatti Aaltonen int r;
242383268a8SMatti Aaltonen
243383268a8SMatti Aaltonen r = i2c_add_driver(&wl1273_core_driver);
244383268a8SMatti Aaltonen if (r) {
245383268a8SMatti Aaltonen pr_err(WL1273_FM_DRIVER_NAME
246383268a8SMatti Aaltonen ": driver registration failed\n");
247383268a8SMatti Aaltonen return r;
248383268a8SMatti Aaltonen }
249383268a8SMatti Aaltonen
250383268a8SMatti Aaltonen return r;
251383268a8SMatti Aaltonen }
252383268a8SMatti Aaltonen
wl1273_core_exit(void)253383268a8SMatti Aaltonen static void __exit wl1273_core_exit(void)
254383268a8SMatti Aaltonen {
255383268a8SMatti Aaltonen i2c_del_driver(&wl1273_core_driver);
256383268a8SMatti Aaltonen }
257383268a8SMatti Aaltonen late_initcall(wl1273_core_init);
258383268a8SMatti Aaltonen module_exit(wl1273_core_exit);
259383268a8SMatti Aaltonen
260383268a8SMatti Aaltonen MODULE_AUTHOR("Matti Aaltonen <matti.j.aaltonen@nokia.com>");
261383268a8SMatti Aaltonen MODULE_DESCRIPTION(DRIVER_DESC);
262383268a8SMatti Aaltonen MODULE_LICENSE("GPL");
263