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