xref: /openbmc/linux/drivers/mfd/wl1273-core.c (revision 9816d859)
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