1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2cc35bbddSJoonyoung Shim /*
3cc35bbddSJoonyoung Shim * drivers/media/radio/si470x/radio-si470x-i2c.c
4cc35bbddSJoonyoung Shim *
5cc35bbddSJoonyoung Shim * I2C driver for radios with Silicon Labs Si470x FM Radio Receivers
6cc35bbddSJoonyoung Shim *
79dcb79c2STobias Lorenz * Copyright (c) 2009 Samsung Electronics Co.Ltd
8cc35bbddSJoonyoung Shim * Author: Joonyoung Shim <jy0922.shim@samsung.com>
9cc35bbddSJoonyoung Shim */
10cc35bbddSJoonyoung Shim
119dcb79c2STobias Lorenz
129dcb79c2STobias Lorenz /* driver definitions */
139dcb79c2STobias Lorenz #define DRIVER_AUTHOR "Joonyoung Shim <jy0922.shim@samsung.com>";
142908249fSKees Cook #define DRIVER_CARD "Silicon Labs Si470x FM Radio"
159dcb79c2STobias Lorenz #define DRIVER_DESC "I2C radio driver for Si470x FM Radio Receivers"
1629834c1aSMauro Carvalho Chehab #define DRIVER_VERSION "1.0.2"
179dcb79c2STobias Lorenz
189dcb79c2STobias Lorenz /* kernel includes */
19cc35bbddSJoonyoung Shim #include <linux/i2c.h>
205a0e3ad6STejun Heo #include <linux/slab.h>
21cc35bbddSJoonyoung Shim #include <linux/delay.h>
221c64222bSPawe? Chmiel #include <linux/gpio/consumer.h>
23fe2137ddSJoonyoung Shim #include <linux/interrupt.h>
24cc35bbddSJoonyoung Shim
25cc35bbddSJoonyoung Shim #include "radio-si470x.h"
26cc35bbddSJoonyoung Shim
27cc35bbddSJoonyoung Shim
289dcb79c2STobias Lorenz /* I2C Device ID List */
299dcb79c2STobias Lorenz static const struct i2c_device_id si470x_i2c_id[] = {
309dcb79c2STobias Lorenz /* Generic Entry */
319dcb79c2STobias Lorenz { "si470x", 0 },
329dcb79c2STobias Lorenz /* Terminating entry */
339dcb79c2STobias Lorenz { }
349dcb79c2STobias Lorenz };
359dcb79c2STobias Lorenz MODULE_DEVICE_TABLE(i2c, si470x_i2c_id);
369dcb79c2STobias Lorenz
379dcb79c2STobias Lorenz
389dcb79c2STobias Lorenz /**************************************************************************
399dcb79c2STobias Lorenz * Module Parameters
409dcb79c2STobias Lorenz **************************************************************************/
419dcb79c2STobias Lorenz
429dcb79c2STobias Lorenz /* Radio Nr */
439dcb79c2STobias Lorenz static int radio_nr = -1;
449dcb79c2STobias Lorenz module_param(radio_nr, int, 0444);
459dcb79c2STobias Lorenz MODULE_PARM_DESC(radio_nr, "Radio Nr");
469dcb79c2STobias Lorenz
47fe2137ddSJoonyoung Shim /* RDS buffer blocks */
48fe2137ddSJoonyoung Shim static unsigned int rds_buf = 100;
49fe2137ddSJoonyoung Shim module_param(rds_buf, uint, 0444);
50fe2137ddSJoonyoung Shim MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
51fe2137ddSJoonyoung Shim
52fe2137ddSJoonyoung Shim /* RDS maximum block errors */
53fe2137ddSJoonyoung Shim static unsigned short max_rds_errors = 1;
54fe2137ddSJoonyoung Shim /* 0 means 0 errors requiring correction */
55fe2137ddSJoonyoung Shim /* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
56fe2137ddSJoonyoung Shim /* 2 means 3-5 errors requiring correction */
57fe2137ddSJoonyoung Shim /* 3 means 6+ errors or errors in checkword, correction not possible */
58fe2137ddSJoonyoung Shim module_param(max_rds_errors, ushort, 0644);
59fe2137ddSJoonyoung Shim MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
60fe2137ddSJoonyoung Shim
619dcb79c2STobias Lorenz
629dcb79c2STobias Lorenz
639dcb79c2STobias Lorenz /**************************************************************************
649dcb79c2STobias Lorenz * I2C Definitions
659dcb79c2STobias Lorenz **************************************************************************/
669dcb79c2STobias Lorenz
679dcb79c2STobias Lorenz /* Write starts with the upper byte of register 0x02 */
689dcb79c2STobias Lorenz #define WRITE_REG_NUM 8
699dcb79c2STobias Lorenz #define WRITE_INDEX(i) (i + 0x02)
709dcb79c2STobias Lorenz
719dcb79c2STobias Lorenz /* Read starts with the upper byte of register 0x0a */
72cc35bbddSJoonyoung Shim #define READ_REG_NUM RADIO_REGISTER_NUM
73cc35bbddSJoonyoung Shim #define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
74cc35bbddSJoonyoung Shim
75cc35bbddSJoonyoung Shim
76cc35bbddSJoonyoung Shim
779dcb79c2STobias Lorenz /**************************************************************************
789dcb79c2STobias Lorenz * General Driver Functions - REGISTERs
799dcb79c2STobias Lorenz **************************************************************************/
80cc35bbddSJoonyoung Shim
819dcb79c2STobias Lorenz /*
829dcb79c2STobias Lorenz * si470x_get_register - read register
839dcb79c2STobias Lorenz */
si470x_get_register(struct si470x_device * radio,int regnr)8458757984SMauro Carvalho Chehab static int si470x_get_register(struct si470x_device *radio, int regnr)
85cc35bbddSJoonyoung Shim {
8690db5c82SMauro Carvalho Chehab __be16 buf[READ_REG_NUM];
87cc35bbddSJoonyoung Shim struct i2c_msg msgs[1] = {
88058fef68SShubhrajyoti D {
89058fef68SShubhrajyoti D .addr = radio->client->addr,
90058fef68SShubhrajyoti D .flags = I2C_M_RD,
91058fef68SShubhrajyoti D .len = sizeof(u16) * READ_REG_NUM,
92058fef68SShubhrajyoti D .buf = (void *)buf
93058fef68SShubhrajyoti D },
94cc35bbddSJoonyoung Shim };
95cc35bbddSJoonyoung Shim
96cc35bbddSJoonyoung Shim if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
97cc35bbddSJoonyoung Shim return -EIO;
98cc35bbddSJoonyoung Shim
99cc35bbddSJoonyoung Shim radio->registers[regnr] = __be16_to_cpu(buf[READ_INDEX(regnr)]);
100cc35bbddSJoonyoung Shim
101cc35bbddSJoonyoung Shim return 0;
102cc35bbddSJoonyoung Shim }
103cc35bbddSJoonyoung Shim
104cc35bbddSJoonyoung Shim
1059dcb79c2STobias Lorenz /*
1069dcb79c2STobias Lorenz * si470x_set_register - write register
1079dcb79c2STobias Lorenz */
si470x_set_register(struct si470x_device * radio,int regnr)10858757984SMauro Carvalho Chehab static int si470x_set_register(struct si470x_device *radio, int regnr)
109cc35bbddSJoonyoung Shim {
110cc35bbddSJoonyoung Shim int i;
11190db5c82SMauro Carvalho Chehab __be16 buf[WRITE_REG_NUM];
112cc35bbddSJoonyoung Shim struct i2c_msg msgs[1] = {
113058fef68SShubhrajyoti D {
114058fef68SShubhrajyoti D .addr = radio->client->addr,
115058fef68SShubhrajyoti D .len = sizeof(u16) * WRITE_REG_NUM,
116058fef68SShubhrajyoti D .buf = (void *)buf
117058fef68SShubhrajyoti D },
118cc35bbddSJoonyoung Shim };
119cc35bbddSJoonyoung Shim
120cc35bbddSJoonyoung Shim for (i = 0; i < WRITE_REG_NUM; i++)
121cc35bbddSJoonyoung Shim buf[i] = __cpu_to_be16(radio->registers[WRITE_INDEX(i)]);
122cc35bbddSJoonyoung Shim
123cc35bbddSJoonyoung Shim if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
124cc35bbddSJoonyoung Shim return -EIO;
125cc35bbddSJoonyoung Shim
126cc35bbddSJoonyoung Shim return 0;
127cc35bbddSJoonyoung Shim }
128cc35bbddSJoonyoung Shim
1299dcb79c2STobias Lorenz
1309dcb79c2STobias Lorenz
1319dcb79c2STobias Lorenz /**************************************************************************
1329dcb79c2STobias Lorenz * General Driver Functions - ENTIRE REGISTERS
1339dcb79c2STobias Lorenz **************************************************************************/
1349dcb79c2STobias Lorenz
1359dcb79c2STobias Lorenz /*
1369dcb79c2STobias Lorenz * si470x_get_all_registers - read entire registers
1379dcb79c2STobias Lorenz */
si470x_get_all_registers(struct si470x_device * radio)1389dcb79c2STobias Lorenz static int si470x_get_all_registers(struct si470x_device *radio)
1399dcb79c2STobias Lorenz {
1409dcb79c2STobias Lorenz int i;
14190db5c82SMauro Carvalho Chehab __be16 buf[READ_REG_NUM];
1429dcb79c2STobias Lorenz struct i2c_msg msgs[1] = {
143058fef68SShubhrajyoti D {
144058fef68SShubhrajyoti D .addr = radio->client->addr,
145058fef68SShubhrajyoti D .flags = I2C_M_RD,
146058fef68SShubhrajyoti D .len = sizeof(u16) * READ_REG_NUM,
147058fef68SShubhrajyoti D .buf = (void *)buf
148058fef68SShubhrajyoti D },
1499dcb79c2STobias Lorenz };
1509dcb79c2STobias Lorenz
1519dcb79c2STobias Lorenz if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
1529dcb79c2STobias Lorenz return -EIO;
1539dcb79c2STobias Lorenz
1549dcb79c2STobias Lorenz for (i = 0; i < READ_REG_NUM; i++)
1559dcb79c2STobias Lorenz radio->registers[i] = __be16_to_cpu(buf[READ_INDEX(i)]);
1569dcb79c2STobias Lorenz
1579dcb79c2STobias Lorenz return 0;
1589dcb79c2STobias Lorenz }
1599dcb79c2STobias Lorenz
1609dcb79c2STobias Lorenz
1619dcb79c2STobias Lorenz
1629dcb79c2STobias Lorenz /**************************************************************************
1639dcb79c2STobias Lorenz * File Operations Interface
1649dcb79c2STobias Lorenz **************************************************************************/
1659dcb79c2STobias Lorenz
1669dcb79c2STobias Lorenz /*
1679dcb79c2STobias Lorenz * si470x_fops_open - file open
1689dcb79c2STobias Lorenz */
si470x_fops_open(struct file * file)16958757984SMauro Carvalho Chehab static int si470x_fops_open(struct file *file)
170cc35bbddSJoonyoung Shim {
171cc35bbddSJoonyoung Shim struct si470x_device *radio = video_drvdata(file);
1724967d53dSHans Verkuil int retval = v4l2_fh_open(file);
173cc35bbddSJoonyoung Shim
1744967d53dSHans Verkuil if (retval)
1754967d53dSHans Verkuil return retval;
176cc35bbddSJoonyoung Shim
1774967d53dSHans Verkuil if (v4l2_fh_is_singular_file(file)) {
178cc35bbddSJoonyoung Shim /* start radio */
179cc35bbddSJoonyoung Shim retval = si470x_start(radio);
180fe2137ddSJoonyoung Shim if (retval < 0)
181fe2137ddSJoonyoung Shim goto done;
1829dcb79c2STobias Lorenz
1830830be3fSJoonyoung Shim /* enable RDS / STC interrupt */
184fe2137ddSJoonyoung Shim radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN;
1850830be3fSJoonyoung Shim radio->registers[SYSCONFIG1] |= SYSCONFIG1_STCIEN;
186fe2137ddSJoonyoung Shim radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;
187fe2137ddSJoonyoung Shim radio->registers[SYSCONFIG1] |= 0x1 << 2;
188fe2137ddSJoonyoung Shim retval = si470x_set_register(radio, SYSCONFIG1);
189fe2137ddSJoonyoung Shim }
190fe2137ddSJoonyoung Shim
191fe2137ddSJoonyoung Shim done:
1924967d53dSHans Verkuil if (retval)
1934967d53dSHans Verkuil v4l2_fh_release(file);
194cc35bbddSJoonyoung Shim return retval;
195cc35bbddSJoonyoung Shim }
196cc35bbddSJoonyoung Shim
1979dcb79c2STobias Lorenz
1989dcb79c2STobias Lorenz /*
1999dcb79c2STobias Lorenz * si470x_fops_release - file release
2009dcb79c2STobias Lorenz */
si470x_fops_release(struct file * file)20158757984SMauro Carvalho Chehab static int si470x_fops_release(struct file *file)
202cc35bbddSJoonyoung Shim {
203cc35bbddSJoonyoung Shim struct si470x_device *radio = video_drvdata(file);
204cc35bbddSJoonyoung Shim
2054967d53dSHans Verkuil if (v4l2_fh_is_singular_file(file))
206cc35bbddSJoonyoung Shim /* stop radio */
2074967d53dSHans Verkuil si470x_stop(radio);
2089dcb79c2STobias Lorenz
2094967d53dSHans Verkuil return v4l2_fh_release(file);
210cc35bbddSJoonyoung Shim }
211cc35bbddSJoonyoung Shim
2129dcb79c2STobias Lorenz
2139dcb79c2STobias Lorenz
2149dcb79c2STobias Lorenz /**************************************************************************
2159dcb79c2STobias Lorenz * Video4Linux Interface
2169dcb79c2STobias Lorenz **************************************************************************/
2179dcb79c2STobias Lorenz
2189dcb79c2STobias Lorenz /*
2199dcb79c2STobias Lorenz * si470x_vidioc_querycap - query device capabilities
2209dcb79c2STobias Lorenz */
si470x_vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * capability)22158757984SMauro Carvalho Chehab static int si470x_vidioc_querycap(struct file *file, void *priv,
222cc35bbddSJoonyoung Shim struct v4l2_capability *capability)
223cc35bbddSJoonyoung Shim {
224c0decac1SMauro Carvalho Chehab strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
225c0decac1SMauro Carvalho Chehab strscpy(capability->card, DRIVER_CARD, sizeof(capability->card));
226cc35bbddSJoonyoung Shim return 0;
227cc35bbddSJoonyoung Shim }
228cc35bbddSJoonyoung Shim
2299dcb79c2STobias Lorenz
2309dcb79c2STobias Lorenz
2319dcb79c2STobias Lorenz /**************************************************************************
2329dcb79c2STobias Lorenz * I2C Interface
2339dcb79c2STobias Lorenz **************************************************************************/
2349dcb79c2STobias Lorenz
2359dcb79c2STobias Lorenz /*
236474fdc08SJoonyoung Shim * si470x_i2c_interrupt - interrupt handler
237fe2137ddSJoonyoung Shim */
si470x_i2c_interrupt(int irq,void * dev_id)238474fdc08SJoonyoung Shim static irqreturn_t si470x_i2c_interrupt(int irq, void *dev_id)
239fe2137ddSJoonyoung Shim {
240474fdc08SJoonyoung Shim struct si470x_device *radio = dev_id;
241fe2137ddSJoonyoung Shim unsigned char regnr;
242fe2137ddSJoonyoung Shim unsigned char blocknum;
243fe2137ddSJoonyoung Shim unsigned short bler; /* rds block errors */
244fe2137ddSJoonyoung Shim unsigned short rds;
245fe2137ddSJoonyoung Shim unsigned char tmpbuf[3];
246fe2137ddSJoonyoung Shim int retval = 0;
247fe2137ddSJoonyoung Shim
2480830be3fSJoonyoung Shim /* check Seek/Tune Complete */
2490830be3fSJoonyoung Shim retval = si470x_get_register(radio, STATUSRSSI);
2500830be3fSJoonyoung Shim if (retval < 0)
251474fdc08SJoonyoung Shim goto end;
2520830be3fSJoonyoung Shim
2530830be3fSJoonyoung Shim if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
2540830be3fSJoonyoung Shim complete(&radio->completion);
2550830be3fSJoonyoung Shim
256fe2137ddSJoonyoung Shim /* safety checks */
257fe2137ddSJoonyoung Shim if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
258474fdc08SJoonyoung Shim goto end;
259fe2137ddSJoonyoung Shim
260fe2137ddSJoonyoung Shim /* Update RDS registers */
2610830be3fSJoonyoung Shim for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) {
262fe2137ddSJoonyoung Shim retval = si470x_get_register(radio, STATUSRSSI + regnr);
263fe2137ddSJoonyoung Shim if (retval < 0)
264474fdc08SJoonyoung Shim goto end;
265fe2137ddSJoonyoung Shim }
266fe2137ddSJoonyoung Shim
267fe2137ddSJoonyoung Shim /* get rds blocks */
268fe2137ddSJoonyoung Shim if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0)
269fe2137ddSJoonyoung Shim /* No RDS group ready, better luck next time */
270474fdc08SJoonyoung Shim goto end;
271fe2137ddSJoonyoung Shim
272fe2137ddSJoonyoung Shim for (blocknum = 0; blocknum < 4; blocknum++) {
273fe2137ddSJoonyoung Shim switch (blocknum) {
274fe2137ddSJoonyoung Shim default:
275fe2137ddSJoonyoung Shim bler = (radio->registers[STATUSRSSI] &
276fe2137ddSJoonyoung Shim STATUSRSSI_BLERA) >> 9;
277fe2137ddSJoonyoung Shim rds = radio->registers[RDSA];
278fe2137ddSJoonyoung Shim break;
279fe2137ddSJoonyoung Shim case 1:
280fe2137ddSJoonyoung Shim bler = (radio->registers[READCHAN] &
281fe2137ddSJoonyoung Shim READCHAN_BLERB) >> 14;
282fe2137ddSJoonyoung Shim rds = radio->registers[RDSB];
283fe2137ddSJoonyoung Shim break;
284fe2137ddSJoonyoung Shim case 2:
285fe2137ddSJoonyoung Shim bler = (radio->registers[READCHAN] &
286fe2137ddSJoonyoung Shim READCHAN_BLERC) >> 12;
287fe2137ddSJoonyoung Shim rds = radio->registers[RDSC];
288fe2137ddSJoonyoung Shim break;
289fe2137ddSJoonyoung Shim case 3:
290fe2137ddSJoonyoung Shim bler = (radio->registers[READCHAN] &
291fe2137ddSJoonyoung Shim READCHAN_BLERD) >> 10;
292fe2137ddSJoonyoung Shim rds = radio->registers[RDSD];
293fe2137ddSJoonyoung Shim break;
294c2c1b415SPeter Senna Tschudin }
295fe2137ddSJoonyoung Shim
296fe2137ddSJoonyoung Shim /* Fill the V4L2 RDS buffer */
297fe2137ddSJoonyoung Shim put_unaligned_le16(rds, &tmpbuf);
298fe2137ddSJoonyoung Shim tmpbuf[2] = blocknum; /* offset name */
299fe2137ddSJoonyoung Shim tmpbuf[2] |= blocknum << 3; /* received offset */
300fe2137ddSJoonyoung Shim if (bler > max_rds_errors)
301fe2137ddSJoonyoung Shim tmpbuf[2] |= 0x80; /* uncorrectable errors */
302fe2137ddSJoonyoung Shim else if (bler > 0)
303fe2137ddSJoonyoung Shim tmpbuf[2] |= 0x40; /* corrected error(s) */
304fe2137ddSJoonyoung Shim
305fe2137ddSJoonyoung Shim /* copy RDS block to internal buffer */
306fe2137ddSJoonyoung Shim memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
307fe2137ddSJoonyoung Shim radio->wr_index += 3;
308fe2137ddSJoonyoung Shim
309fe2137ddSJoonyoung Shim /* wrap write pointer */
310fe2137ddSJoonyoung Shim if (radio->wr_index >= radio->buf_size)
311fe2137ddSJoonyoung Shim radio->wr_index = 0;
312fe2137ddSJoonyoung Shim
313fe2137ddSJoonyoung Shim /* check for overflow */
314fe2137ddSJoonyoung Shim if (radio->wr_index == radio->rd_index) {
315fe2137ddSJoonyoung Shim /* increment and wrap read pointer */
316fe2137ddSJoonyoung Shim radio->rd_index += 3;
317fe2137ddSJoonyoung Shim if (radio->rd_index >= radio->buf_size)
318fe2137ddSJoonyoung Shim radio->rd_index = 0;
319fe2137ddSJoonyoung Shim }
320fe2137ddSJoonyoung Shim }
321fe2137ddSJoonyoung Shim
322fe2137ddSJoonyoung Shim if (radio->wr_index != radio->rd_index)
323fe2137ddSJoonyoung Shim wake_up_interruptible(&radio->read_queue);
324fe2137ddSJoonyoung Shim
325474fdc08SJoonyoung Shim end:
326fe2137ddSJoonyoung Shim return IRQ_HANDLED;
327fe2137ddSJoonyoung Shim }
328fe2137ddSJoonyoung Shim
329fe2137ddSJoonyoung Shim
330fe2137ddSJoonyoung Shim /*
3319dcb79c2STobias Lorenz * si470x_i2c_probe - probe for the device
3329dcb79c2STobias Lorenz */
si470x_i2c_probe(struct i2c_client * client)3335e568089SKieran Bingham static int si470x_i2c_probe(struct i2c_client *client)
334cc35bbddSJoonyoung Shim {
335cc35bbddSJoonyoung Shim struct si470x_device *radio;
336cc35bbddSJoonyoung Shim int retval = 0;
337cc35bbddSJoonyoung Shim
338cc35bbddSJoonyoung Shim /* private data allocation and initialization */
339f86c51b6SPawe? Chmiel radio = devm_kzalloc(&client->dev, sizeof(*radio), GFP_KERNEL);
340cc35bbddSJoonyoung Shim if (!radio) {
341cc35bbddSJoonyoung Shim retval = -ENOMEM;
342cc35bbddSJoonyoung Shim goto err_initial;
343cc35bbddSJoonyoung Shim }
344fe2137ddSJoonyoung Shim
3459dcb79c2STobias Lorenz radio->client = client;
346f140612dSHans de Goede radio->band = 1; /* Default to 76 - 108 MHz */
347cc35bbddSJoonyoung Shim mutex_init(&radio->lock);
34877947111SHans de Goede init_completion(&radio->completion);
349cc35bbddSJoonyoung Shim
35058757984SMauro Carvalho Chehab radio->get_register = si470x_get_register;
35158757984SMauro Carvalho Chehab radio->set_register = si470x_set_register;
35258757984SMauro Carvalho Chehab radio->fops_open = si470x_fops_open;
35358757984SMauro Carvalho Chehab radio->fops_release = si470x_fops_release;
35458757984SMauro Carvalho Chehab radio->vidioc_querycap = si470x_vidioc_querycap;
35558757984SMauro Carvalho Chehab
3568c081b6fSDouglas Fischer retval = v4l2_device_register(&client->dev, &radio->v4l2_dev);
3578c081b6fSDouglas Fischer if (retval < 0) {
3588c081b6fSDouglas Fischer dev_err(&client->dev, "couldn't register v4l2_device\n");
359f86c51b6SPawe? Chmiel goto err_initial;
3608c081b6fSDouglas Fischer }
3618c081b6fSDouglas Fischer
3628c081b6fSDouglas Fischer v4l2_ctrl_handler_init(&radio->hdl, 2);
3638c081b6fSDouglas Fischer v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
3648c081b6fSDouglas Fischer V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
3658c081b6fSDouglas Fischer v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
3668c081b6fSDouglas Fischer V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
3678c081b6fSDouglas Fischer if (radio->hdl.error) {
3688c081b6fSDouglas Fischer retval = radio->hdl.error;
3698c081b6fSDouglas Fischer dev_err(&client->dev, "couldn't register control\n");
370ef054e34SYang Yingliang goto err_all;
3718c081b6fSDouglas Fischer }
3728c081b6fSDouglas Fischer
3734967d53dSHans Verkuil /* video device initialization */
3744967d53dSHans Verkuil radio->videodev = si470x_viddev_template;
3758c081b6fSDouglas Fischer radio->videodev.ctrl_handler = &radio->hdl;
3768c081b6fSDouglas Fischer radio->videodev.lock = &radio->lock;
3778c081b6fSDouglas Fischer radio->videodev.v4l2_dev = &radio->v4l2_dev;
3788c081b6fSDouglas Fischer radio->videodev.release = video_device_release_empty;
379e83ce300SHans Verkuil radio->videodev.device_caps =
380e83ce300SHans Verkuil V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER |
381e83ce300SHans Verkuil V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
3824967d53dSHans Verkuil video_set_drvdata(&radio->videodev, radio);
383cc35bbddSJoonyoung Shim
3841c64222bSPawe? Chmiel radio->gpio_reset = devm_gpiod_get_optional(&client->dev, "reset",
3851c64222bSPawe? Chmiel GPIOD_OUT_LOW);
3861c64222bSPawe? Chmiel if (IS_ERR(radio->gpio_reset)) {
3871c64222bSPawe? Chmiel retval = PTR_ERR(radio->gpio_reset);
3881c64222bSPawe? Chmiel dev_err(&client->dev, "Failed to request gpio: %d\n", retval);
3891c64222bSPawe? Chmiel goto err_all;
3901c64222bSPawe? Chmiel }
3911c64222bSPawe? Chmiel
3921c64222bSPawe? Chmiel if (radio->gpio_reset)
3931c64222bSPawe? Chmiel gpiod_set_value(radio->gpio_reset, 1);
3941c64222bSPawe? Chmiel
395cc35bbddSJoonyoung Shim /* power up : need 110ms */
396cc35bbddSJoonyoung Shim radio->registers[POWERCFG] = POWERCFG_ENABLE;
397cc35bbddSJoonyoung Shim if (si470x_set_register(radio, POWERCFG) < 0) {
398cc35bbddSJoonyoung Shim retval = -EIO;
399f86c51b6SPawe? Chmiel goto err_all;
400cc35bbddSJoonyoung Shim }
401cc35bbddSJoonyoung Shim msleep(110);
402cc35bbddSJoonyoung Shim
4039dcb79c2STobias Lorenz /* get device and chip versions */
404cc35bbddSJoonyoung Shim if (si470x_get_all_registers(radio) < 0) {
405cc35bbddSJoonyoung Shim retval = -EIO;
406f86c51b6SPawe? Chmiel goto err_all;
407cc35bbddSJoonyoung Shim }
408cc35bbddSJoonyoung Shim dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
409dd7a2acfSMauro Carvalho Chehab radio->registers[DEVICEID], radio->registers[SI_CHIPID]);
410dd7a2acfSMauro Carvalho Chehab if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
4119dcb79c2STobias Lorenz dev_warn(&client->dev,
412d5aa19c9SMauro Carvalho Chehab "This driver is known to work with firmware version %u, but the device has firmware version %u.\n"
413d5aa19c9SMauro Carvalho Chehab "If you have some trouble using this driver, please report to V4L ML at linux-media@vger.kernel.org\n",
414d5aa19c9SMauro Carvalho Chehab RADIO_FW_VERSION,
415dd7a2acfSMauro Carvalho Chehab radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE);
4169dcb79c2STobias Lorenz }
417cc35bbddSJoonyoung Shim
418cc35bbddSJoonyoung Shim /* set initial frequency */
419cc35bbddSJoonyoung Shim si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
420cc35bbddSJoonyoung Shim
421fe2137ddSJoonyoung Shim /* rds buffer allocation */
422fe2137ddSJoonyoung Shim radio->buf_size = rds_buf * 3;
423f86c51b6SPawe? Chmiel radio->buffer = devm_kmalloc(&client->dev, radio->buf_size, GFP_KERNEL);
424fe2137ddSJoonyoung Shim if (!radio->buffer) {
425fe2137ddSJoonyoung Shim retval = -EIO;
426f86c51b6SPawe? Chmiel goto err_all;
427fe2137ddSJoonyoung Shim }
428fe2137ddSJoonyoung Shim
429fe2137ddSJoonyoung Shim /* rds buffer configuration */
430fe2137ddSJoonyoung Shim radio->wr_index = 0;
431fe2137ddSJoonyoung Shim radio->rd_index = 0;
432fe2137ddSJoonyoung Shim init_waitqueue_head(&radio->read_queue);
433fe2137ddSJoonyoung Shim
434f86c51b6SPawe? Chmiel retval = devm_request_threaded_irq(&client->dev, client->irq, NULL,
435f86c51b6SPawe? Chmiel si470x_i2c_interrupt,
436f86c51b6SPawe? Chmiel IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
437f86c51b6SPawe? Chmiel DRIVER_NAME, radio);
438fe2137ddSJoonyoung Shim if (retval) {
439fe2137ddSJoonyoung Shim dev_err(&client->dev, "Failed to register interrupt\n");
440f86c51b6SPawe? Chmiel goto err_all;
441fe2137ddSJoonyoung Shim }
442fe2137ddSJoonyoung Shim
443cc35bbddSJoonyoung Shim /* register video device */
4444967d53dSHans Verkuil retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
4459dcb79c2STobias Lorenz radio_nr);
446cc35bbddSJoonyoung Shim if (retval) {
447cc35bbddSJoonyoung Shim dev_warn(&client->dev, "Could not register video device\n");
448cc35bbddSJoonyoung Shim goto err_all;
449cc35bbddSJoonyoung Shim }
450cc35bbddSJoonyoung Shim i2c_set_clientdata(client, radio);
451cc35bbddSJoonyoung Shim
452cc35bbddSJoonyoung Shim return 0;
453cc35bbddSJoonyoung Shim err_all:
4548c081b6fSDouglas Fischer v4l2_ctrl_handler_free(&radio->hdl);
4558c081b6fSDouglas Fischer v4l2_device_unregister(&radio->v4l2_dev);
456cc35bbddSJoonyoung Shim err_initial:
457cc35bbddSJoonyoung Shim return retval;
458cc35bbddSJoonyoung Shim }
459cc35bbddSJoonyoung Shim
4609dcb79c2STobias Lorenz
4619dcb79c2STobias Lorenz /*
4629dcb79c2STobias Lorenz * si470x_i2c_remove - remove the device
4639dcb79c2STobias Lorenz */
si470x_i2c_remove(struct i2c_client * client)464ed5c2f5fSUwe Kleine-König static void si470x_i2c_remove(struct i2c_client *client)
465cc35bbddSJoonyoung Shim {
466cc35bbddSJoonyoung Shim struct si470x_device *radio = i2c_get_clientdata(client);
467cc35bbddSJoonyoung Shim
4684967d53dSHans Verkuil video_unregister_device(&radio->videodev);
469cc35bbddSJoonyoung Shim
4701c64222bSPawe? Chmiel if (radio->gpio_reset)
4711c64222bSPawe? Chmiel gpiod_set_value(radio->gpio_reset, 0);
4721c64222bSPawe? Chmiel
4732df200abSChuhong Yuan v4l2_ctrl_handler_free(&radio->hdl);
4742df200abSChuhong Yuan v4l2_device_unregister(&radio->v4l2_dev);
475cc35bbddSJoonyoung Shim }
476cc35bbddSJoonyoung Shim
477cc35bbddSJoonyoung Shim
478c6c3795eSMauro Carvalho Chehab #ifdef CONFIG_PM_SLEEP
479d1471f02SJoonyoung Shim /*
480d1471f02SJoonyoung Shim * si470x_i2c_suspend - suspend the device
481d1471f02SJoonyoung Shim */
si470x_i2c_suspend(struct device * dev)482949cf31cSJoonyoung Shim static int si470x_i2c_suspend(struct device *dev)
483d1471f02SJoonyoung Shim {
484949cf31cSJoonyoung Shim struct i2c_client *client = to_i2c_client(dev);
485d1471f02SJoonyoung Shim struct si470x_device *radio = i2c_get_clientdata(client);
486d1471f02SJoonyoung Shim
487d1471f02SJoonyoung Shim /* power down */
488d1471f02SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_DISABLE;
489d1471f02SJoonyoung Shim if (si470x_set_register(radio, POWERCFG) < 0)
490d1471f02SJoonyoung Shim return -EIO;
491d1471f02SJoonyoung Shim
492d1471f02SJoonyoung Shim return 0;
493d1471f02SJoonyoung Shim }
494d1471f02SJoonyoung Shim
495d1471f02SJoonyoung Shim
496d1471f02SJoonyoung Shim /*
497d1471f02SJoonyoung Shim * si470x_i2c_resume - resume the device
498d1471f02SJoonyoung Shim */
si470x_i2c_resume(struct device * dev)499949cf31cSJoonyoung Shim static int si470x_i2c_resume(struct device *dev)
500d1471f02SJoonyoung Shim {
501949cf31cSJoonyoung Shim struct i2c_client *client = to_i2c_client(dev);
502d1471f02SJoonyoung Shim struct si470x_device *radio = i2c_get_clientdata(client);
503d1471f02SJoonyoung Shim
504d1471f02SJoonyoung Shim /* power up : need 110ms */
505d1471f02SJoonyoung Shim radio->registers[POWERCFG] |= POWERCFG_ENABLE;
506d1471f02SJoonyoung Shim if (si470x_set_register(radio, POWERCFG) < 0)
507d1471f02SJoonyoung Shim return -EIO;
508d1471f02SJoonyoung Shim msleep(110);
509d1471f02SJoonyoung Shim
510d1471f02SJoonyoung Shim return 0;
511d1471f02SJoonyoung Shim }
512949cf31cSJoonyoung Shim
513949cf31cSJoonyoung Shim static SIMPLE_DEV_PM_OPS(si470x_i2c_pm, si470x_i2c_suspend, si470x_i2c_resume);
514d1471f02SJoonyoung Shim #endif
515d1471f02SJoonyoung Shim
51695f9db59SPawe? Chmiel #if IS_ENABLED(CONFIG_OF)
51795f9db59SPawe? Chmiel static const struct of_device_id si470x_of_match[] = {
51895f9db59SPawe? Chmiel { .compatible = "silabs,si470x" },
51995f9db59SPawe? Chmiel { },
52095f9db59SPawe? Chmiel };
52195f9db59SPawe? Chmiel MODULE_DEVICE_TABLE(of, si470x_of_match);
52295f9db59SPawe? Chmiel #endif
523d1471f02SJoonyoung Shim
5249dcb79c2STobias Lorenz /*
5259dcb79c2STobias Lorenz * si470x_i2c_driver - i2c driver interface
5269dcb79c2STobias Lorenz */
527cc35bbddSJoonyoung Shim static struct i2c_driver si470x_i2c_driver = {
528cc35bbddSJoonyoung Shim .driver = {
529cc35bbddSJoonyoung Shim .name = "si470x",
53095f9db59SPawe? Chmiel .of_match_table = of_match_ptr(si470x_of_match),
531c6c3795eSMauro Carvalho Chehab #ifdef CONFIG_PM_SLEEP
532949cf31cSJoonyoung Shim .pm = &si470x_i2c_pm,
533949cf31cSJoonyoung Shim #endif
534cc35bbddSJoonyoung Shim },
535*aaeb31c0SUwe Kleine-König .probe = si470x_i2c_probe,
5364c62e976SGreg Kroah-Hartman .remove = si470x_i2c_remove,
537cc35bbddSJoonyoung Shim .id_table = si470x_i2c_id,
538cc35bbddSJoonyoung Shim };
539cc35bbddSJoonyoung Shim
540c6e8d86fSAxel Lin module_i2c_driver(si470x_i2c_driver);
541cc35bbddSJoonyoung Shim
542cc35bbddSJoonyoung Shim MODULE_LICENSE("GPL");
5439dcb79c2STobias Lorenz MODULE_AUTHOR(DRIVER_AUTHOR);
5449dcb79c2STobias Lorenz MODULE_DESCRIPTION(DRIVER_DESC);
5459dcb79c2STobias Lorenz MODULE_VERSION(DRIVER_VERSION);
546