
The value returned by an i2c driver's remove function is mostly ignored, so the prototype of the remove function was changed to return no value. So change the related remove callback return void. Change-Id: Iba2d83fe7f11ddfaabc67171b3ed7ee6955680be
1017 lines
25 KiB
C
1017 lines
25 KiB
C
/* drivers/media/radio/rtc6226/radio-rtc6226-i2c.c
|
|
*
|
|
* Driver for Richwave RTC6226 FM Tuner
|
|
*
|
|
* Copyright (c) 2009 Samsung Electronics Co.Ltd
|
|
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
|
|
* Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
|
|
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
|
|
* Copyright (c) 2018 LG Electronics, Inc.
|
|
* Copyright (c) 2018 Richwave Technology Co.Ltd
|
|
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* kernel includes */
|
|
#include <linux/i2c.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include "radio-rtc6226.h"
|
|
#include <linux/workqueue.h>
|
|
#include <linux/version.h>
|
|
|
|
static const struct of_device_id rtc6226_i2c_dt_ids[] = {
|
|
{.compatible = "rtc6226"},
|
|
{}
|
|
};
|
|
|
|
/* I2C Device ID List */
|
|
static const struct i2c_device_id rtc6226_i2c_id[] = {
|
|
/* Generic Entry */
|
|
{ "rtc6226", 0 },
|
|
/* Terminating entry */
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, rtc6226_i2c_id);
|
|
|
|
|
|
/**************************************************************************
|
|
* Module Parameters
|
|
**************************************************************************/
|
|
|
|
/* Radio Nr */
|
|
static int radio_nr = -1;
|
|
MODULE_PARM_DESC(radio_nr, "Radio Nr");
|
|
|
|
/* RDS buffer blocks */
|
|
static unsigned int rds_buf = 100;
|
|
MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
|
|
|
|
enum rtc6226_ctrl_id {
|
|
RTC6226_ID_CSR0_ENABLE,
|
|
RTC6226_ID_CSR0_DISABLE,
|
|
RTC6226_ID_DEVICEID,
|
|
RTC6226_ID_CSR0_DIS_SMUTE,
|
|
RTC6226_ID_CSR0_DIS_MUTE,
|
|
RTC6226_ID_CSR0_DEEM,
|
|
RTC6226_ID_CSR0_BLNDADJUST,
|
|
RTC6226_ID_CSR0_VOLUME,
|
|
RTC6226_ID_CSR0_BAND,
|
|
RTC6226_ID_CSR0_CHSPACE,
|
|
RTC6226_ID_CSR0_DIS_AGC,
|
|
RTC6226_ID_CSR0_RDS_EN,
|
|
RTC6226_ID_SEEK_CANCEL,
|
|
RTC6226_ID_CSR0_SEEKRSSITH,
|
|
RTC6226_ID_CSR0_OFSTH,
|
|
RTC6226_ID_CSR0_QLTTH,
|
|
RTC6226_ID_RSSI,
|
|
RTC6226_ID_RDS_RDY,
|
|
RTC6226_ID_STD,
|
|
RTC6226_ID_SF,
|
|
RTC6226_ID_RDS_SYNC,
|
|
RTC6226_ID_SI,
|
|
};
|
|
|
|
|
|
/**************************************************************************
|
|
* I2C Definitions
|
|
**************************************************************************/
|
|
/* Write starts with the upper byte of register 0x02 */
|
|
#define WRITE_REG_NUM 3
|
|
#define WRITE_INDEX(i) ((i + 0x02)%16)
|
|
|
|
/* Read starts with the upper byte of register 0x0a */
|
|
#define READ_REG_NUM 2
|
|
#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
|
|
|
|
/*static*/
|
|
struct tasklet_struct my_tasklet;
|
|
/*
|
|
* rtc6226_get_register - read register
|
|
*/
|
|
int rtc6226_get_register(struct rtc6226_device *radio, int regnr)
|
|
{
|
|
u8 reg[1];
|
|
u8 buf[READ_REG_NUM];
|
|
struct i2c_msg msgs[2] = {
|
|
{ radio->client->addr, 0, 1, reg },
|
|
{ radio->client->addr, I2C_M_RD, sizeof(buf), buf },
|
|
};
|
|
|
|
reg[0] = (u8)(regnr);
|
|
if (i2c_transfer(radio->client->adapter, msgs, 2) != 2)
|
|
return -EIO;
|
|
|
|
radio->registers[regnr] =
|
|
(u16)(((buf[0] << 8) & 0xff00) | buf[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_set_register - write register
|
|
*/
|
|
int rtc6226_set_register(struct rtc6226_device *radio, int regnr)
|
|
{
|
|
u8 buf[WRITE_REG_NUM];
|
|
struct i2c_msg msgs[1] = {
|
|
{ radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM,
|
|
(void *)buf },
|
|
};
|
|
|
|
buf[0] = (u8)(regnr);
|
|
buf[1] = (u8)((radio->registers[(u8)(regnr) & 0xFF] >> 8) & 0xFF);
|
|
buf[2] = (u8)(radio->registers[(u8)(regnr) & 0xFF] & 0xFF);
|
|
|
|
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_set_register - write register
|
|
*/
|
|
int rtc6226_set_serial_registers(struct rtc6226_device *radio,
|
|
u16 *data, int regnr)
|
|
{
|
|
u8 buf[WRITE_REG_NUM];
|
|
struct i2c_msg msgs[1] = {
|
|
{ radio->client->addr, 0, sizeof(u8) * WRITE_REG_NUM,
|
|
(void *)buf },
|
|
};
|
|
|
|
buf[0] = (u8)(regnr);
|
|
buf[1] = (u8)((data[0] >> 8) & 0xFF);
|
|
buf[2] = (u8)(data[0] & 0xFF);
|
|
|
|
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* General Driver Functions - ENTIRE REGISTERS
|
|
**************************************************************************/
|
|
/*
|
|
* rtc6226_get_all_registers - read entire registers
|
|
*/
|
|
/* changed from static */
|
|
int rtc6226_get_all_registers(struct rtc6226_device *radio)
|
|
{
|
|
int i;
|
|
int err;
|
|
u8 reg[1] = {0x00};
|
|
u8 buf[RADIO_REGISTER_NUM];
|
|
struct i2c_msg msgs1[1] = {
|
|
{ radio->client->addr, 0, 1, reg},
|
|
};
|
|
struct i2c_msg msgs[1] = {
|
|
{ radio->client->addr, I2C_M_RD, sizeof(buf), buf },
|
|
};
|
|
|
|
if (i2c_transfer(radio->client->adapter, msgs1, 1) != 1)
|
|
return -EIO;
|
|
|
|
err = i2c_transfer(radio->client->adapter, msgs, 1);
|
|
|
|
if (err < 0)
|
|
return -EIO;
|
|
|
|
for (i = 0; i < 16; i++)
|
|
radio->registers[i] =
|
|
(u16)(((buf[i*2] << 8) & 0xff00) | buf[i*2+1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_vidioc_querycap - query device capabilities
|
|
*/
|
|
int rtc6226_vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *capability)
|
|
{
|
|
FMDBG("%s enter\n", __func__);
|
|
strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
|
|
strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
|
|
capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE |
|
|
V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
|
|
capability->capabilities = capability->device_caps |
|
|
V4L2_CAP_DEVICE_CAPS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_i2c_interrupt - interrupt handler
|
|
*/
|
|
static void rtc6226_i2c_interrupt_handler(struct rtc6226_device *radio)
|
|
{
|
|
unsigned char regnr;
|
|
int retval = 0;
|
|
unsigned short current_chan;
|
|
|
|
FMDBG("%s enter\n", __func__);
|
|
|
|
/* check Seek/Tune Complete */
|
|
retval = rtc6226_get_register(radio, STATUS);
|
|
if (retval < 0) {
|
|
FMDERR("%s read fail to STATUS\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
if (radio->registers[STATUS] & STATUS_STD) {
|
|
FMDBG("%s : STATUS=0x%4.4hx\n", __func__,
|
|
radio->registers[STATUS]);
|
|
|
|
retval = rtc6226_get_register(radio, RSSI);
|
|
if (retval < 0) {
|
|
FMDERR("%s read fail to RSSI\n", __func__);
|
|
goto end;
|
|
}
|
|
FMDBG("%s : RSSI=0x%4.4hx\n", __func__, radio->registers[RSSI]);
|
|
/* stop seeking : clear STD*/
|
|
radio->registers[SEEKCFG1] &= ~SEEKCFG1_CSR0_SEEK;
|
|
retval = rtc6226_set_register(radio, SEEKCFG1);
|
|
/*clear the status bit to allow another tune or seek*/
|
|
current_chan = radio->registers[CHANNEL] & CHANNEL_CSR0_CH;
|
|
radio->registers[CHANNEL] &= ~CHANNEL_CSR0_TUNE;
|
|
retval = rtc6226_set_register(radio, CHANNEL);
|
|
if (retval < 0)
|
|
radio->registers[CHANNEL] = current_chan;
|
|
rtc6226_reset_rds_data(radio);
|
|
FMDBG("%s clear Seek/Tune bit\n", __func__);
|
|
if (radio->seek_tune_status == SEEK_PENDING) {
|
|
/* Enable the RDS as it was disabled before seek */
|
|
rtc6226_rds_on(radio);
|
|
FMDBG("posting RTC6226_EVT_SEEK_COMPLETE event\n");
|
|
rtc6226_q_event(radio, RTC6226_EVT_SEEK_COMPLETE);
|
|
/* post tune comp evt since seek results in a tune.*/
|
|
FMDBG("posting RICHWAVE_EVT_TUNE_SUCC event\n");
|
|
rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
|
|
radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
|
|
} else if (radio->seek_tune_status == TUNE_PENDING) {
|
|
FMDBG("posting RICHWAVE_EVT_TUNE_SUCC event\n");
|
|
rtc6226_q_event(radio, RTC6226_EVT_TUNE_SUCC);
|
|
radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
|
|
} else if (radio->seek_tune_status == SCAN_PENDING) {
|
|
/* when scan is pending and STC int is set, signal
|
|
* so that scan can proceed
|
|
*/
|
|
FMDBG("In %s, signalling scan thread\n", __func__);
|
|
complete(&radio->completion);
|
|
}
|
|
FMDBG("%s Seek/Tune done\n", __func__);
|
|
} else {
|
|
/* Check RDS data after tune/seek interrupt finished
|
|
* Update RDS registers
|
|
*/
|
|
for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) {
|
|
retval = rtc6226_get_register(radio, STATUS + regnr);
|
|
if (retval < 0)
|
|
goto end;
|
|
}
|
|
/* get rds blocks */
|
|
if ((radio->registers[STATUS] & STATUS_RDS_RDY) == 0) {
|
|
/* No RDS group ready, better luck next time */
|
|
FMDERR("%s No RDS group ready\n", __func__);
|
|
goto end;
|
|
} else {
|
|
/* avoid RDS interrupt lock disable_irq*/
|
|
if ((radio->registers[SYSCFG] &
|
|
SYSCFG_CSR0_RDS_EN) != 0) {
|
|
schedule_work(&radio->rds_worker);
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
FMDBG("%s exit :%d\n", __func__, retval);
|
|
}
|
|
|
|
static irqreturn_t rtc6226_isr(int irq, void *dev_id)
|
|
{
|
|
struct rtc6226_device *radio = dev_id;
|
|
/*
|
|
* The call to queue_delayed_work ensures that a minimum delay
|
|
* (in jiffies) passes before the work is actually executed. The return
|
|
* value from the function is nonzero if the work_struct was actually
|
|
* added to queue (otherwise, it may have already been there and will
|
|
* not be added a second time).
|
|
*/
|
|
|
|
queue_delayed_work(radio->wqueue, &radio->work,
|
|
msecs_to_jiffies(10));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void rtc6226_handler(struct work_struct *work)
|
|
{
|
|
struct rtc6226_device *radio;
|
|
|
|
radio = container_of(work, struct rtc6226_device, work.work);
|
|
|
|
rtc6226_i2c_interrupt_handler(radio);
|
|
}
|
|
|
|
void rtc6226_disable_irq(struct rtc6226_device *radio)
|
|
{
|
|
int irq;
|
|
|
|
irq = radio->irq;
|
|
disable_irq_wake(irq);
|
|
free_irq(irq, radio);
|
|
|
|
cancel_delayed_work_sync(&radio->work);
|
|
flush_workqueue(radio->wqueue);
|
|
|
|
cancel_work_sync(&radio->rds_worker);
|
|
flush_workqueue(radio->wqueue_rds);
|
|
cancel_delayed_work_sync(&radio->work_scan);
|
|
flush_workqueue(radio->wqueue_scan);
|
|
}
|
|
|
|
int rtc6226_enable_irq(struct rtc6226_device *radio)
|
|
{
|
|
int retval;
|
|
int irq;
|
|
|
|
retval = gpio_direction_input(radio->int_gpio);
|
|
if (retval) {
|
|
FMDERR("%s unable to set the gpio %d direction(%d)\n",
|
|
__func__, radio->int_gpio, retval);
|
|
return retval;
|
|
}
|
|
radio->irq = gpio_to_irq(radio->int_gpio);
|
|
irq = radio->irq;
|
|
|
|
if (radio->irq < 0) {
|
|
FMDERR("%s: gpio_to_irq returned %d\n", __func__, radio->irq);
|
|
goto open_err_req_irq;
|
|
}
|
|
|
|
FMDBG("%s irq number is = %d\n", __func__, radio->irq);
|
|
|
|
retval = request_any_context_irq(radio->irq, rtc6226_isr,
|
|
IRQF_TRIGGER_FALLING, DRIVER_NAME, radio);
|
|
|
|
if (retval < 0) {
|
|
FMDERR("%s Couldn't acquire FM gpio %d, retval:%d\n",
|
|
__func__, radio->irq, retval);
|
|
goto open_err_req_irq;
|
|
} else {
|
|
FMDBG("%s FM GPIO %d registered\n", __func__, radio->irq);
|
|
}
|
|
retval = enable_irq_wake(irq);
|
|
if (retval < 0) {
|
|
FMDERR("Could not wake FM interrupt\n");
|
|
free_irq(irq, radio);
|
|
}
|
|
return retval;
|
|
|
|
open_err_req_irq:
|
|
rtc6226_disable_irq(radio);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int rtc6226_fm_vio_reg_cfg(struct rtc6226_device *radio, bool on)
|
|
{
|
|
int rc = 0;
|
|
struct fm_power_vreg_data *vreg;
|
|
|
|
vreg = radio->vioreg;
|
|
if (!vreg) {
|
|
FMDERR("In %s, vio reg is NULL\n", __func__);
|
|
return rc;
|
|
}
|
|
if (on) {
|
|
FMDBG("vreg is : %s\n", vreg->name);
|
|
rc = regulator_set_voltage(vreg->reg,
|
|
vreg->low_vol_level,
|
|
vreg->high_vol_level);
|
|
if (rc < 0) {
|
|
FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
rc = regulator_enable(vreg->reg);
|
|
if (rc < 0) {
|
|
FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
|
|
regulator_set_voltage(vreg->reg,
|
|
0,
|
|
vreg->high_vol_level);
|
|
return rc;
|
|
}
|
|
vreg->is_enabled = true;
|
|
|
|
} else {
|
|
rc = regulator_disable(vreg->reg);
|
|
if (rc < 0) {
|
|
FMDERR("reg disable(%s) fail rc=%d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
vreg->is_enabled = false;
|
|
|
|
/* Set the min voltage to 0 */
|
|
rc = regulator_set_voltage(vreg->reg,
|
|
0,
|
|
vreg->high_vol_level);
|
|
if (rc < 0) {
|
|
FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int rtc6226_fm_vdd_reg_cfg(struct rtc6226_device *radio, bool on)
|
|
{
|
|
int rc = 0;
|
|
struct fm_power_vreg_data *vreg;
|
|
|
|
vreg = radio->vddreg;
|
|
if (!vreg) {
|
|
FMDERR("In %s, vdd reg is NULL\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
if (on) {
|
|
FMDBG("vreg is : %s\n", vreg->name);
|
|
rc = regulator_set_voltage(vreg->reg,
|
|
vreg->low_vol_level,
|
|
vreg->high_vol_level);
|
|
if (rc < 0) {
|
|
FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
if (vreg->vdd_load) {
|
|
rc = regulator_set_load(vreg->reg, vreg->vdd_load);
|
|
if (rc < 0) {
|
|
FMDERR("%s Unable to set the load %d ,err=%d\n",
|
|
__func__, vreg->vdd_load, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = regulator_enable(vreg->reg);
|
|
if (rc < 0) {
|
|
FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
|
|
regulator_set_voltage(vreg->reg,
|
|
0,
|
|
vreg->high_vol_level);
|
|
return rc;
|
|
}
|
|
vreg->is_enabled = true;
|
|
} else {
|
|
rc = regulator_disable(vreg->reg);
|
|
if (rc < 0) {
|
|
FMDERR("reg disable(%s) fail. rc=%d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
vreg->is_enabled = false;
|
|
|
|
/* Set the min voltage to 0 */
|
|
rc = regulator_set_voltage(vreg->reg,
|
|
0,
|
|
vreg->high_vol_level);
|
|
if (rc < 0) {
|
|
FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
|
|
return rc;
|
|
}
|
|
if (vreg->vdd_load) {
|
|
rc = regulator_set_load(vreg->reg, 0);
|
|
if (rc < 0) {
|
|
FMDERR("%s Unable to set the load 0 ,err=%d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int rtc6226_fm_power_cfg(struct rtc6226_device *radio, bool powerflag)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (powerflag) {
|
|
/* Turn ON sequence */
|
|
rc = rtc6226_fm_vdd_reg_cfg(radio, powerflag);
|
|
if (rc < 0) {
|
|
FMDERR("In %s, vdd reg cfg failed %x\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
rc = rtc6226_fm_vio_reg_cfg(radio, powerflag);
|
|
if (rc < 0) {
|
|
FMDERR("In %s, vio reg cfg failed %x\n", __func__, rc);
|
|
rtc6226_fm_vdd_reg_cfg(radio, false);
|
|
return rc;
|
|
}
|
|
} else {
|
|
/* Turn OFF sequence */
|
|
rc = rtc6226_fm_vdd_reg_cfg(radio, powerflag);
|
|
if (rc < 0)
|
|
FMDERR("In %s, vdd reg cfg failed %x\n", __func__, rc);
|
|
rc = rtc6226_fm_vio_reg_cfg(radio, powerflag);
|
|
if (rc < 0)
|
|
FMDERR("In %s, vio reg cfg failed %x\n", __func__, rc);
|
|
}
|
|
return rc;
|
|
}
|
|
/*
|
|
* rtc6226_fops_open - file open
|
|
*/
|
|
int rtc6226_fops_open(struct file *file)
|
|
{
|
|
struct rtc6226_device *radio = video_drvdata(file);
|
|
int retval;
|
|
|
|
FMDBG("%s enter user num = %d\n", __func__, radio->users);
|
|
if (atomic_inc_return(&radio->users) != 1) {
|
|
FMDERR("Device already in use. Try again later\n");
|
|
atomic_dec(&radio->users);
|
|
return -EBUSY;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&radio->work, rtc6226_handler);
|
|
INIT_DELAYED_WORK(&radio->work_scan, rtc6226_scan);
|
|
INIT_WORK(&radio->rds_worker, rtc6226_rds_handler);
|
|
|
|
/* Power up Supply voltage to VDD and VIO */
|
|
retval = rtc6226_fm_power_cfg(radio, TURNING_ON);
|
|
if (retval) {
|
|
FMDERR("%s: failed to supply voltage\n", __func__);
|
|
goto open_err_setup;
|
|
}
|
|
|
|
retval = rtc6226_enable_irq(radio);
|
|
/* Wait for the value to take effect on gpio. */
|
|
msleep(100);
|
|
if (retval) {
|
|
FMDERR("%s:enable irq failed\n", __func__);
|
|
goto open_err_req_irq;
|
|
}
|
|
return retval;
|
|
|
|
open_err_req_irq:
|
|
rtc6226_fm_power_cfg(radio, TURNING_OFF);
|
|
open_err_setup:
|
|
atomic_dec(&radio->users);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_fops_release - file release
|
|
*/
|
|
int rtc6226_fops_release(struct file *file)
|
|
{
|
|
struct rtc6226_device *radio = video_drvdata(file);
|
|
int retval = 0;
|
|
|
|
FMDBG("%s : Exit\n", __func__);
|
|
if (radio->mode != FM_OFF) {
|
|
rtc6226_power_down(radio);
|
|
radio->mode = FM_OFF;
|
|
}
|
|
rtc6226_disable_irq(radio);
|
|
atomic_dec(&radio->users);
|
|
retval = rtc6226_fm_power_cfg(radio, TURNING_OFF);
|
|
if (retval < 0)
|
|
FMDERR("%s: failed to apply voltage\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
static int rtc6226_parse_dt(struct device *dev,
|
|
struct rtc6226_device *radio)
|
|
{
|
|
int rc = 0;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
radio->int_gpio = of_get_named_gpio(np, "fmint-gpio", 0);
|
|
if (radio->int_gpio < 0) {
|
|
FMDERR("%s int-gpio not provided in device tree\n", __func__);
|
|
rc = radio->int_gpio;
|
|
goto err_int_gpio;
|
|
}
|
|
|
|
rc = gpio_request(radio->int_gpio, "fm_int");
|
|
if (rc) {
|
|
FMDERR("%s unable to request gpio %d (%d)\n", __func__,
|
|
radio->int_gpio, rc);
|
|
goto err_int_gpio;
|
|
}
|
|
|
|
rc = gpio_direction_output(radio->int_gpio, 0);
|
|
if (rc) {
|
|
FMDERR("%s unable to set the gpio %d direction(%d)\n",
|
|
__func__, radio->int_gpio, rc);
|
|
goto err_int_gpio;
|
|
}
|
|
/* Wait for the value to take effect on gpio. */
|
|
msleep(100);
|
|
|
|
return rc;
|
|
|
|
err_int_gpio:
|
|
gpio_free(radio->int_gpio);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int rtc6226_pinctrl_init(struct rtc6226_device *radio)
|
|
{
|
|
int retval = 0;
|
|
|
|
radio->fm_pinctrl = devm_pinctrl_get(&radio->client->dev);
|
|
if (IS_ERR_OR_NULL(radio->fm_pinctrl)) {
|
|
FMDERR("%s: target does not use pinctrl\n", __func__);
|
|
retval = PTR_ERR(radio->fm_pinctrl);
|
|
return retval;
|
|
}
|
|
|
|
radio->gpio_state_active =
|
|
pinctrl_lookup_state(radio->fm_pinctrl,
|
|
"pmx_fm_active");
|
|
if (IS_ERR_OR_NULL(radio->gpio_state_active)) {
|
|
FMDERR("%s: cannot get FM active state\n", __func__);
|
|
retval = PTR_ERR(radio->gpio_state_active);
|
|
goto err_active_state;
|
|
}
|
|
|
|
radio->gpio_state_suspend =
|
|
pinctrl_lookup_state(radio->fm_pinctrl,
|
|
"pmx_fm_suspend");
|
|
if (IS_ERR_OR_NULL(radio->gpio_state_suspend)) {
|
|
FMDERR("%s: cannot get FM suspend state\n", __func__);
|
|
retval = PTR_ERR(radio->gpio_state_suspend);
|
|
goto err_suspend_state;
|
|
}
|
|
|
|
return retval;
|
|
|
|
err_suspend_state:
|
|
radio->gpio_state_suspend = 0;
|
|
|
|
err_active_state:
|
|
radio->gpio_state_active = 0;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int rtc6226_dt_parse_vreg_info(struct device *dev,
|
|
struct fm_power_vreg_data *vreg, const char *vreg_name)
|
|
{
|
|
int ret = 0;
|
|
u32 vol_suply[2];
|
|
struct device_node *np = dev->of_node;
|
|
|
|
ret = of_property_read_u32_array(np, vreg_name, vol_suply, 2);
|
|
if (ret < 0) {
|
|
FMDERR("Invalid property name\n");
|
|
ret = -EINVAL;
|
|
} else {
|
|
vreg->low_vol_level = vol_suply[0];
|
|
vreg->high_vol_level = vol_suply[1];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_i2c_probe - probe for the device
|
|
*/
|
|
static int rtc6226_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct rtc6226_device *radio;
|
|
struct v4l2_device *v4l2_dev;
|
|
struct v4l2_ctrl_handler *hdl;
|
|
struct regulator *vddvreg = NULL;
|
|
struct regulator *viovreg = NULL;
|
|
int retval = 0;
|
|
int i = 0;
|
|
int kfifo_alloc_rc = 0;
|
|
|
|
/* struct v4l2_ctrl *ctrl; */
|
|
/* need to add description "irq-fm" in dts */
|
|
|
|
FMDBG("%s enter\n", __func__);
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
retval = -ENODEV;
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* if voltage regulator is not ready yet, return the error
|
|
* if error is -EPROBE_DEFER to kernel then probe will be called at
|
|
* later point of time.
|
|
*/
|
|
viovreg = regulator_get(&client->dev, "vio");
|
|
if (IS_ERR(viovreg)) {
|
|
retval = PTR_ERR(viovreg);
|
|
FMDERR("%s: regulator_get(vio) failed. retval=%d\n",
|
|
__func__, retval);
|
|
return retval;
|
|
}
|
|
|
|
vddvreg = regulator_get(&client->dev, "vdd");
|
|
if (IS_ERR(vddvreg)) {
|
|
retval = PTR_ERR(vddvreg);
|
|
FMDERR("%s: regulator_get(vdd) failed. retval=%d\n",
|
|
__func__, retval);
|
|
regulator_put(viovreg);
|
|
return retval;
|
|
}
|
|
|
|
/* private data allocation and initialization */
|
|
radio = kzalloc(sizeof(struct rtc6226_device), GFP_KERNEL);
|
|
if (!radio) {
|
|
retval = -ENOMEM;
|
|
regulator_put(viovreg);
|
|
regulator_put(vddvreg);
|
|
return retval;
|
|
}
|
|
|
|
v4l2_dev = &radio->v4l2_dev;
|
|
retval = v4l2_device_register(&client->dev, v4l2_dev);
|
|
if (retval < 0) {
|
|
FMDERR("%s couldn't register v4l2_device\n", __func__);
|
|
goto err_vreg;
|
|
}
|
|
|
|
FMDBG("v4l2_device_register successfully\n");
|
|
hdl = &radio->ctrl_handler;
|
|
|
|
/* initialize the device count */
|
|
atomic_set(&radio->users, 0);
|
|
radio->client = client;
|
|
mutex_init(&radio->lock);
|
|
init_completion(&radio->completion);
|
|
|
|
retval = rtc6226_parse_dt(&client->dev, radio);
|
|
if (retval) {
|
|
FMDERR("%s: Parsing DT failed(%d)\n", __func__, retval);
|
|
goto err_v4l2;
|
|
}
|
|
|
|
radio->vddreg = devm_kzalloc(&client->dev,
|
|
sizeof(struct fm_power_vreg_data),
|
|
GFP_KERNEL);
|
|
if (!radio->vddreg) {
|
|
FMDERR("%s: allocating memory for vdd vreg failed\n",
|
|
__func__);
|
|
retval = -ENOMEM;
|
|
goto err_v4l2;
|
|
}
|
|
|
|
radio->vddreg->reg = vddvreg;
|
|
radio->vddreg->name = "vdd";
|
|
radio->vddreg->is_enabled = false;
|
|
of_property_read_u32(client->dev.of_node,
|
|
"rtc6226,vdd-load", &radio->vddreg->vdd_load);
|
|
FMDERR("%s: rtc6226,vdd-load val %d\n",
|
|
__func__, radio->vddreg->vdd_load);
|
|
retval = rtc6226_dt_parse_vreg_info(&client->dev,
|
|
radio->vddreg, "rtc6226,vdd-supply-voltage");
|
|
if (retval < 0) {
|
|
FMDERR("%s: parsing vdd-supply failed\n", __func__);
|
|
goto err_v4l2;
|
|
}
|
|
|
|
radio->vioreg = devm_kzalloc(&client->dev,
|
|
sizeof(struct fm_power_vreg_data),
|
|
GFP_KERNEL);
|
|
if (!radio->vioreg) {
|
|
FMDERR("%s: allocating memory for vio vreg failed\n",
|
|
__func__);
|
|
retval = -ENOMEM;
|
|
goto err_v4l2;
|
|
}
|
|
radio->vioreg->reg = viovreg;
|
|
radio->vioreg->name = "vio";
|
|
radio->vioreg->is_enabled = false;
|
|
retval = rtc6226_dt_parse_vreg_info(&client->dev,
|
|
radio->vioreg, "rtc6226,vio-supply-voltage");
|
|
if (retval < 0) {
|
|
FMDERR("%s: parsing vio-supply failed\n", __func__);
|
|
goto err_v4l2;
|
|
}
|
|
/* Initialize pin control*/
|
|
retval = rtc6226_pinctrl_init(radio);
|
|
if (retval) {
|
|
FMDERR("%s: rtc6226_pinctrl_init returned %d\n",
|
|
__func__, retval);
|
|
/* if pinctrl is not supported, -EINVAL is returned*/
|
|
if (retval == -EINVAL)
|
|
retval = 0;
|
|
} else {
|
|
FMDBG("%s rtc6226_pinctrl_init success\n", __func__);
|
|
}
|
|
|
|
memcpy(&radio->videodev, &rtc6226_viddev_template,
|
|
sizeof(struct video_device));
|
|
|
|
radio->videodev.v4l2_dev = v4l2_dev;
|
|
radio->videodev.ioctl_ops = &rtc6226_ioctl_ops;
|
|
radio->videodev.device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE
|
|
| V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
|
|
video_set_drvdata(&radio->videodev, radio);
|
|
|
|
/* rds buffer allocation */
|
|
radio->buf_size = rds_buf * 3;
|
|
radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
|
|
if (!radio->buffer) {
|
|
retval = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
for (i = 0; i < RTC6226_FM_BUF_MAX; i++) {
|
|
spin_lock_init(&radio->buf_lock[i]);
|
|
|
|
kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
|
|
STD_BUF_SIZE, GFP_KERNEL);
|
|
|
|
if (kfifo_alloc_rc != 0) {
|
|
FMDERR("%s: failed allocating buffers %d\n",
|
|
__func__, kfifo_alloc_rc);
|
|
retval = -ENOMEM;
|
|
goto err_rds;
|
|
}
|
|
}
|
|
radio->wqueue = NULL;
|
|
radio->wqueue_scan = NULL;
|
|
radio->wqueue_rds = NULL;
|
|
radio->band = -1;
|
|
|
|
/* rds buffer configuration */
|
|
radio->wr_index = 0;
|
|
radio->rd_index = 0;
|
|
init_waitqueue_head(&radio->event_queue);
|
|
init_waitqueue_head(&radio->read_queue);
|
|
init_waitqueue_head(&rtc6226_wq);
|
|
|
|
radio->wqueue = create_singlethread_workqueue("fmradio");
|
|
if (!radio->wqueue) {
|
|
retval = -ENOMEM;
|
|
goto err_rds;
|
|
}
|
|
|
|
radio->wqueue_scan = create_singlethread_workqueue("fmradioscan");
|
|
if (!radio->wqueue_scan) {
|
|
retval = -ENOMEM;
|
|
goto err_wqueue;
|
|
}
|
|
|
|
radio->wqueue_rds = create_singlethread_workqueue("fmradiords");
|
|
if (!radio->wqueue_rds) {
|
|
retval = -ENOMEM;
|
|
goto err_wqueue_scan;
|
|
}
|
|
|
|
/* register video device */
|
|
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
|
|
radio_nr);
|
|
if (retval) {
|
|
dev_info(&client->dev, "Could not register video device\n");
|
|
goto err_all;
|
|
}
|
|
|
|
i2c_set_clientdata(client, radio); /* move from below */
|
|
FMDBG("%s exit\n", __func__);
|
|
return 0;
|
|
|
|
err_all:
|
|
destroy_workqueue(radio->wqueue_rds);
|
|
err_wqueue_scan:
|
|
destroy_workqueue(radio->wqueue_scan);
|
|
err_wqueue:
|
|
destroy_workqueue(radio->wqueue);
|
|
err_rds:
|
|
kfree(radio->buffer);
|
|
err:
|
|
video_device_release_empty(&radio->videodev);
|
|
err_v4l2:
|
|
v4l2_device_unregister(v4l2_dev);
|
|
err_vreg:
|
|
if (radio && radio->vioreg && radio->vioreg->reg) {
|
|
regulator_put(radio->vioreg->reg);
|
|
devm_kfree(&client->dev, radio->vioreg);
|
|
} else {
|
|
regulator_put(viovreg);
|
|
}
|
|
if (radio && radio->vddreg && radio->vddreg->reg) {
|
|
regulator_put(radio->vddreg->reg);
|
|
devm_kfree(&client->dev, radio->vddreg);
|
|
} else {
|
|
regulator_put(vddvreg);
|
|
}
|
|
kfree(radio);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* rtc6226_i2c_remove - remove the device
|
|
*/
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0))
|
|
static void rtc6226_i2c_remove(struct i2c_client *client)
|
|
#else
|
|
static int rtc6226_i2c_remove(struct i2c_client *client)
|
|
#endif
|
|
{
|
|
struct rtc6226_device *radio = i2c_get_clientdata(client);
|
|
|
|
free_irq(client->irq, radio);
|
|
kfree(radio->buffer);
|
|
v4l2_ctrl_handler_free(&radio->ctrl_handler);
|
|
if (video_is_registered(&radio->videodev))
|
|
video_unregister_device(&radio->videodev);
|
|
video_device_release_empty(&radio->videodev);
|
|
v4l2_device_unregister(&radio->v4l2_dev);
|
|
kfree(radio);
|
|
FMDBG("%s exit\n", __func__);
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0))
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
/*
|
|
* rtc6226_i2c_suspend - suspend the device
|
|
*/
|
|
static int rtc6226_i2c_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct rtc6226_device *radio = i2c_get_clientdata(client);
|
|
|
|
FMDBG("%s %d\n", __func__, radio->client->addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* rtc6226_i2c_resume - resume the device
|
|
*/
|
|
static int rtc6226_i2c_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct rtc6226_device *radio = i2c_get_clientdata(client);
|
|
|
|
FMDBG("%s %d\n", __func__, radio->client->addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(rtc6226_i2c_pm, rtc6226_i2c_suspend,
|
|
rtc6226_i2c_resume);
|
|
#endif
|
|
|
|
|
|
/*
|
|
* rtc6226_i2c_driver - i2c driver interface
|
|
*/
|
|
struct i2c_driver rtc6226_i2c_driver = {
|
|
.driver = {
|
|
.name = "rtc6226",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(rtc6226_i2c_dt_ids),
|
|
#ifdef CONFIG_PM
|
|
.pm = &rtc6226_i2c_pm,
|
|
#endif
|
|
},
|
|
.probe = rtc6226_i2c_probe,
|
|
.remove = rtc6226_i2c_remove,
|
|
.id_table = rtc6226_i2c_id,
|
|
};
|
|
|
|
/*
|
|
* rtc6226_i2c_init
|
|
*/
|
|
int rtc6226_i2c_init(void)
|
|
{
|
|
FMDBG(DRIVER_DESC ", Version " DRIVER_VERSION "\n");
|
|
return i2c_add_driver(&rtc6226_i2c_driver);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_VERSION(DRIVER_VERSION);
|