Bring BT family drivers from msm-5.10 kernel to BT kernel project. drivers/bluetooth/btpower.c -> . drivers/bluetooth/btfm* -> . drivers/media/radio/rtc6226/* -> . include/net/bt* -> include/ Above shows how directories and header files are relocated. Change-Id: I6263eaa81dec344d7589f09ad3ec525ee7b04a8a
1011 خطوط
25 KiB
C
1011 خطوط
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>
|
|
|
|
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
|
|
*/
|
|
static int rtc6226_i2c_remove(struct i2c_client *client)
|
|
{
|
|
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__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#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);
|