4668 lines
139 KiB
C
4668 lines
139 KiB
C
/* Copyright (c) 2009-2013, The Linux Foundation. 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 version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
/*
|
|
* Qualcomm Tavarua FM core driver
|
|
*/
|
|
|
|
/* driver definitions */
|
|
#define DRIVER_AUTHOR "Qualcomm"
|
|
#define DRIVER_NAME "radio-tavarua"
|
|
#define DRIVER_CARD "Qualcomm FM Radio Transceiver"
|
|
#define DRIVER_DESC "I2C radio driver for Qualcomm FM Radio Transceiver "
|
|
#define DRIVER_VERSION "1.0.0"
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/init.h> /* Initdata */
|
|
#include <linux/delay.h> /* udelay */
|
|
#include <linux/uaccess.h> /* copy to/from user */
|
|
#include <linux/kfifo.h> /* lock free circular buffer */
|
|
#include <linux/param.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
/* kernel includes */
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/version.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/mutex.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <asm/unaligned.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <linux/unistd.h>
|
|
#include <asm/atomic.h>
|
|
#include <media/tavarua.h>
|
|
#include <linux/mfd/marimba.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/slab.h>
|
|
/*
|
|
regional parameters for radio device
|
|
*/
|
|
struct region_params_t {
|
|
enum tavarua_region_t region;
|
|
unsigned int band_high;
|
|
unsigned int band_low;
|
|
char emphasis;
|
|
char rds_std;
|
|
char spacing;
|
|
};
|
|
|
|
struct srch_params_t {
|
|
unsigned short srch_pi;
|
|
unsigned char srch_pty;
|
|
unsigned int preset_num;
|
|
int get_list;
|
|
};
|
|
|
|
/* Main radio device structure,
|
|
acts as a shadow copy of the
|
|
actual tavaura registers */
|
|
struct tavarua_device {
|
|
struct video_device *videodev;
|
|
/* driver management */
|
|
atomic_t users;
|
|
/* top level driver data */
|
|
struct marimba *marimba;
|
|
struct device *dev;
|
|
/* platform specific functionality */
|
|
struct marimba_fm_platform_data *pdata;
|
|
unsigned int chipID;
|
|
/*RDS buffers + Radio event buffer*/
|
|
struct kfifo data_buf[TAVARUA_BUF_MAX];
|
|
/* search paramters */
|
|
struct srch_params_t srch_params;
|
|
/* keep track of pending xfrs */
|
|
int pending_xfrs[TAVARUA_XFR_MAX];
|
|
int xfr_bytes_left;
|
|
int xfr_in_progress;
|
|
/* Transmit data */
|
|
enum tavarua_xfr_ctrl_t tx_mode;
|
|
/* synchrnous xfr data */
|
|
unsigned char sync_xfr_regs[XFR_REG_NUM];
|
|
struct completion sync_xfr_start;
|
|
struct completion shutdown_done;
|
|
struct completion sync_req_done;
|
|
int tune_req;
|
|
/* internal register status */
|
|
unsigned char registers[RADIO_REGISTERS];
|
|
/* regional settings */
|
|
struct region_params_t region_params;
|
|
/* power mode */
|
|
int lp_mode;
|
|
int handle_irq;
|
|
/* global lock */
|
|
struct mutex lock;
|
|
/* buffer locks*/
|
|
spinlock_t buf_lock[TAVARUA_BUF_MAX];
|
|
/* work queue */
|
|
struct workqueue_struct *wqueue;
|
|
struct delayed_work work;
|
|
/* wait queue for blocking event read */
|
|
wait_queue_head_t event_queue;
|
|
/* wait queue for raw rds read */
|
|
wait_queue_head_t read_queue;
|
|
/* PTY for FM Tx */
|
|
int pty;
|
|
/* PI for FM TX */
|
|
int pi;
|
|
/*PS repeatcount for PS Tx */
|
|
int ps_repeatcount;
|
|
int enable_optimized_srch_alg;
|
|
unsigned char spur_table_size;
|
|
struct fm_spur_data spur_data;
|
|
atomic_t validate_channel;
|
|
unsigned char is_station_valid;
|
|
};
|
|
|
|
/**************************************************************************
|
|
* Module Parameters
|
|
**************************************************************************/
|
|
|
|
/* Radio Nr */
|
|
static int radio_nr = -1;
|
|
module_param(radio_nr, int, 0);
|
|
MODULE_PARM_DESC(radio_nr, "Radio Nr");
|
|
static int wait_timeout = WAIT_TIMEOUT;
|
|
/* Bahama's version*/
|
|
static u8 bahama_version;
|
|
/* RDS buffer blocks */
|
|
static unsigned int rds_buf = 100;
|
|
module_param(rds_buf, uint, 0);
|
|
MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
|
|
/* static variables */
|
|
static struct tavarua_device *private_data;
|
|
/* forward declerations */
|
|
static int tavarua_disable_interrupts(struct tavarua_device *radio);
|
|
static int tavarua_setup_interrupts(struct tavarua_device *radio,
|
|
enum radio_state_t state);
|
|
static int tavarua_start(struct tavarua_device *radio,
|
|
enum radio_state_t state);
|
|
static int tavarua_request_irq(struct tavarua_device *radio);
|
|
static void start_pending_xfr(struct tavarua_device *radio);
|
|
static int update_spur_table(struct tavarua_device *radio);
|
|
static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
|
|
unsigned long offset, unsigned char *buf);
|
|
static int compute_MPX_DCC(struct tavarua_device *radio, int *val);
|
|
|
|
/* work function */
|
|
static void read_int_stat(struct work_struct *work);
|
|
|
|
static int is_bahama(void)
|
|
{
|
|
int id = 0;
|
|
|
|
switch (id = adie_get_detected_connectivity_type()) {
|
|
case BAHAMA_ID:
|
|
FMDBG("It is Bahama\n");
|
|
return 1;
|
|
|
|
case MARIMBA_ID:
|
|
FMDBG("It is Marimba\n");
|
|
return 0;
|
|
default:
|
|
printk(KERN_ERR "%s: unexpected adie connectivity type: %d\n",
|
|
__func__, id);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
static int set_fm_slave_id(struct tavarua_device *radio)
|
|
{
|
|
int bahama_present = is_bahama();
|
|
|
|
if (bahama_present == -ENODEV)
|
|
return -ENODEV;
|
|
|
|
if (bahama_present)
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA_FM;
|
|
else
|
|
radio->marimba->mod_id = MARIMBA_SLAVE_ID_FM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_isr
|
|
=============================================================================*/
|
|
/**
|
|
This function is called when GPIO is toggled. This functions queues the event
|
|
to interrupt queue, which is later handled by isr handling funcion.
|
|
i.e. INIT_DELAYED_WORK(&radio->work, read_int_stat);
|
|
|
|
@param irq: irq that is toggled.
|
|
@param dev_id: structure pointer passed by client.
|
|
|
|
@return IRQ_HANDLED.
|
|
*/
|
|
static irqreturn_t tavarua_isr(int irq, void *dev_id)
|
|
{
|
|
struct tavarua_device *radio = dev_id;
|
|
/* schedule a tasklet to handle host intr */
|
|
/* 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(TAVARUA_DELAY));
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Interface to radio internal registers over top level marimba driver
|
|
*************************************************************************/
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_read_registers
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to read a number of bytes from an I2C interface.
|
|
The bytes read are stored in internal register status (shadow copy).
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param offset: register offset.
|
|
@param len: num of bytes.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_read_registers(struct tavarua_device *radio,
|
|
unsigned char offset, int len)
|
|
{
|
|
int retval = 0, i = 0;
|
|
retval = set_fm_slave_id(radio);
|
|
|
|
if (retval == -ENODEV)
|
|
return retval;
|
|
|
|
FMDBG_I2C("I2C Slave: %x, Read Offset(%x): Data [",
|
|
radio->marimba->mod_id,
|
|
offset);
|
|
|
|
retval = marimba_read(radio->marimba, offset,
|
|
&radio->registers[offset], len);
|
|
|
|
if (retval > 0) {
|
|
for (i = 0; i < len; i++)
|
|
FMDBG_I2C("%02x ", radio->registers[offset+i]);
|
|
FMDBG_I2C(" ]\n");
|
|
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_write_register
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to write a byte over the I2C interface.
|
|
The corresponding shadow copy is stored in internal register status.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param offset: register offset.
|
|
@param value: buffer to be written to the registers.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_write_register(struct tavarua_device *radio,
|
|
unsigned char offset, unsigned char value)
|
|
{
|
|
int retval;
|
|
retval = set_fm_slave_id(radio);
|
|
|
|
if (retval == -ENODEV)
|
|
return retval;
|
|
|
|
FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[",
|
|
radio->marimba->mod_id,
|
|
offset);
|
|
retval = marimba_write(radio->marimba, offset, &value, 1);
|
|
if (retval > 0) {
|
|
if (offset < RADIO_REGISTERS) {
|
|
radio->registers[offset] = value;
|
|
FMDBG_I2C("%02x ", radio->registers[offset]);
|
|
}
|
|
FMDBG_I2C(" ]\n");
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_write_registers
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to write a number of bytes over the I2C interface.
|
|
The corresponding shadow copy is stored in internal register status.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param offset: register offset.
|
|
@param buf: buffer to be written to the registers.
|
|
@param len: num of bytes.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_write_registers(struct tavarua_device *radio,
|
|
unsigned char offset, unsigned char *buf, int len)
|
|
{
|
|
|
|
int i;
|
|
int retval;
|
|
retval = set_fm_slave_id(radio);
|
|
|
|
if (retval == -ENODEV)
|
|
return retval;
|
|
|
|
FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[",
|
|
radio->marimba->mod_id,
|
|
offset);
|
|
retval = marimba_write(radio->marimba, offset, buf, len);
|
|
if (retval > 0) { /* if write successful, update internal state too */
|
|
for (i = 0; i < len; i++) {
|
|
if ((offset+i) < RADIO_REGISTERS) {
|
|
radio->registers[offset+i] = buf[i];
|
|
FMDBG_I2C("%x ", radio->registers[offset+i]);
|
|
}
|
|
}
|
|
FMDBG_I2C(" ]\n");
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: read_data_blocks
|
|
=============================================================================*/
|
|
/**
|
|
This function reads Raw RDS blocks from Core regs to driver
|
|
internal regs (shadow copy).
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param offset: register offset.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int read_data_blocks(struct tavarua_device *radio, unsigned char offset)
|
|
{
|
|
/* read all 3 RDS blocks */
|
|
return tavarua_read_registers(radio, offset, RDS_BLOCK*4);
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_rds_read
|
|
=============================================================================*/
|
|
/**
|
|
This is a rds processing function reads that reads Raw RDS blocks from Core
|
|
regs to driver internal regs (shadow copy). It then fills the V4L2 RDS buffer,
|
|
which is read by App using JNI interface.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return None.
|
|
*/
|
|
static void tavarua_rds_read(struct tavarua_device *radio)
|
|
{
|
|
struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS];
|
|
unsigned char blocknum;
|
|
unsigned char tmp[3];
|
|
|
|
if (read_data_blocks(radio, RAW_RDS) < 0)
|
|
return;
|
|
/* copy all four RDS blocks to internal buffer */
|
|
for (blocknum = 0; blocknum < RDS_BLOCKS_NUM; blocknum++) {
|
|
/* Fill the V4L2 RDS buffer */
|
|
put_unaligned(cpu_to_le16(radio->registers[RAW_RDS +
|
|
blocknum*RDS_BLOCK]), (unsigned short *) tmp);
|
|
tmp[2] = blocknum; /* offset name */
|
|
tmp[2] |= blocknum << 3; /* received offset */
|
|
tmp[2] |= 0x40; /* corrected error(s) */
|
|
|
|
/* copy RDS block to internal buffer */
|
|
kfifo_in_locked(rds_buf, tmp, 3, &radio->buf_lock[TAVARUA_BUF_RAW_RDS]);
|
|
}
|
|
/* wake up read queue */
|
|
if (kfifo_len(rds_buf))
|
|
wake_up_interruptible(&radio->read_queue);
|
|
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: request_read_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function sets the desired MODE in the XFRCTRL register and also sets the
|
|
CTRL field to read.
|
|
This is an asynchronous way of reading the XFR registers. Client would request
|
|
by setting the desired mode in the XFRCTRL register and then would initiate
|
|
the actual data register read by calling copy_from_xfr up on SOC signals
|
|
success.
|
|
|
|
NOTE:
|
|
|
|
The Data Transfer (XFR) registers are used to pass various data and
|
|
configuration parameters between the Core and host processor.
|
|
|
|
To read from the XFR registers, the host processor must set the desired MODE
|
|
in the XFRCTRL register and set the CTRL field to read. The Core will then
|
|
populate the XFRDAT0 - XFRDAT15 registers with the defined mode bytes. The
|
|
Core will set the TRANSFER interrupt status bit and interrupt the host if the
|
|
TRANSFERCTRL interrupt control bit is set. The host can then extract the XFR
|
|
mode bytes once it detects that the Core has updated the registers.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return Always returns 0.
|
|
*/
|
|
static int request_read_xfr(struct tavarua_device *radio,
|
|
enum tavarua_xfr_ctrl_t mode){
|
|
|
|
tavarua_write_register(radio, XFRCTRL, mode);
|
|
msleep(TAVARUA_DELAY);
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: copy_from_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function is used to read XFR mode bytes once it detects that the Core
|
|
has updated the registers. It also updates XFR regs to the appropriate
|
|
internal buffer n bytes.
|
|
|
|
NOTE:
|
|
|
|
This function should be used in conjuction with request_read_xfr. Refer
|
|
request_read_xfr for XFR mode transaction details.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param buf_type: Index into RDS/Radio event buffer to use.
|
|
@param len: num of bytes.
|
|
|
|
@return Always returns 0.
|
|
*/
|
|
static int copy_from_xfr(struct tavarua_device *radio,
|
|
enum tavarua_buf_t buf_type, unsigned int n){
|
|
|
|
struct kfifo *data_fifo = &radio->data_buf[buf_type];
|
|
unsigned char *xfr_regs = &radio->registers[XFRCTRL+1];
|
|
kfifo_in_locked(data_fifo, xfr_regs, n, &radio->buf_lock[buf_type]);
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: write_to_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function sets the desired MODE in the XFRCTRL register and it also sets
|
|
the CTRL field and data to write.
|
|
This also writes all the XFRDATx registers with the desired input buffer.
|
|
|
|
NOTE:
|
|
|
|
The Data Transfer (XFR) registers are used to pass various data and
|
|
configuration parameters between the Core and host processor.
|
|
|
|
To write data to the Core, the host processor updates XFRDAT0 - XFRDAT15 with
|
|
the appropriate mode bytes. The host processor must then set the desired MODE
|
|
in the XFRCTRL register and set the CTRL field to write. The core will detect
|
|
that the XFRCTRL register was written to and will read the XFR mode bytes.
|
|
After reading all the mode bytes, the Core will set the TRANSFER interrupt
|
|
status bit and interrupt the host if the TRANSFERCTRL interrupt control bit
|
|
is set.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param mode: XFR mode to write in XFRCTRL register.
|
|
@param buf: buffer to be written to the registers.
|
|
@param len: num of bytes.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int write_to_xfr(struct tavarua_device *radio, unsigned char mode,
|
|
char *buf, int len)
|
|
{
|
|
char buffer[len+1];
|
|
memcpy(buffer+1, buf, len);
|
|
/* buffer[0] corresponds to XFRCTRL register
|
|
set the CTRL bit to 1 for write mode
|
|
*/
|
|
buffer[0] = ((1<<7) | mode);
|
|
return tavarua_write_registers(radio, XFRCTRL, buffer, sizeof(buffer));
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: xfr_intf_own
|
|
=============================================================================*/
|
|
/**
|
|
This function is used to check if there is any pending XFR mode operation.
|
|
If yes, wait for it to complete, else update the flag to indicate XFR
|
|
operation is in progress
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return 0 on success.
|
|
-ETIME on timeout.
|
|
*/
|
|
static int xfr_intf_own(struct tavarua_device *radio)
|
|
{
|
|
|
|
mutex_lock(&radio->lock);
|
|
if (radio->xfr_in_progress) {
|
|
radio->pending_xfrs[TAVARUA_XFR_SYNC] = 1;
|
|
mutex_unlock(&radio->lock);
|
|
if (!wait_for_completion_timeout(&radio->sync_xfr_start,
|
|
msecs_to_jiffies(wait_timeout)))
|
|
return -ETIME;
|
|
} else {
|
|
FMDBG("gained ownership of xfr\n");
|
|
radio->xfr_in_progress = 1;
|
|
mutex_unlock(&radio->lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: sync_read_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function is used to do synchronous XFR read operation.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param xfr_type: XFR mode to write in XFRCTRL register.
|
|
@param buf: buffer to be read from the core.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int sync_read_xfr(struct tavarua_device *radio,
|
|
enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf)
|
|
{
|
|
int retval;
|
|
retval = xfr_intf_own(radio);
|
|
if (retval < 0)
|
|
return retval;
|
|
retval = tavarua_write_register(radio, XFRCTRL, xfr_type);
|
|
|
|
if (retval >= 0) {
|
|
/* Wait for interrupt i.e. complete
|
|
(&radio->sync_req_done); call */
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout)) || (retval < 0)) {
|
|
retval = -ETIME;
|
|
} else {
|
|
memcpy(buf, radio->sync_xfr_regs, XFR_REG_NUM);
|
|
}
|
|
}
|
|
radio->xfr_in_progress = 0;
|
|
start_pending_xfr(radio);
|
|
FMDBG("%s: %d\n", __func__, retval);
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: sync_write_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function is used to do synchronous XFR write operation.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param xfr_type: XFR mode to write in XFRCTRL register.
|
|
@param buf: buffer to be written to the core.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int sync_write_xfr(struct tavarua_device *radio,
|
|
enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf)
|
|
{
|
|
int retval;
|
|
retval = xfr_intf_own(radio);
|
|
if (retval < 0)
|
|
return retval;
|
|
retval = write_to_xfr(radio, xfr_type, buf, XFR_REG_NUM);
|
|
|
|
if (retval >= 0) {
|
|
/* Wait for interrupt i.e. complete
|
|
(&radio->sync_req_done); call */
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout)) || (retval < 0)) {
|
|
FMDBG("Write xfr timeout");
|
|
}
|
|
}
|
|
radio->xfr_in_progress = 0;
|
|
start_pending_xfr(radio);
|
|
FMDBG("%s: %d\n", __func__, retval);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*=============================================================================
|
|
FUNCTION: start_pending_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function checks if their are any pending xfr interrupts and if
|
|
the interrupts are either RDS PS, RDS RT, RDS AF, SCANNEXT, SEARCH or SYNC
|
|
then initiates corresponding read operation. Preference is given to RAW RDS
|
|
data (SYNC) over processed data (PS, RT, AF, etc) from core.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return None.
|
|
*/
|
|
static void start_pending_xfr(struct tavarua_device *radio)
|
|
{
|
|
int i;
|
|
enum tavarua_xfr_t xfr;
|
|
for (i = 0; i < TAVARUA_XFR_MAX; i++) {
|
|
if (radio->pending_xfrs[i]) {
|
|
radio->xfr_in_progress = 1;
|
|
xfr = (enum tavarua_xfr_t)i;
|
|
switch (xfr) {
|
|
/* priority given to synchronous xfrs */
|
|
case TAVARUA_XFR_SYNC:
|
|
complete(&radio->sync_xfr_start);
|
|
break;
|
|
/* asynchrnous xfrs */
|
|
case TAVARUA_XFR_SRCH_LIST:
|
|
request_read_xfr(radio, RX_STATIONS_0);
|
|
break;
|
|
case TAVARUA_XFR_RT_RDS:
|
|
request_read_xfr(radio, RDS_RT_0);
|
|
break;
|
|
case TAVARUA_XFR_PS_RDS:
|
|
request_read_xfr(radio, RDS_PS_0);
|
|
break;
|
|
case TAVARUA_XFR_AF_LIST:
|
|
request_read_xfr(radio, RDS_AF_0);
|
|
break;
|
|
default:
|
|
FMDERR("%s: Unsupported XFR %d\n",
|
|
__func__, xfr);
|
|
}
|
|
radio->pending_xfrs[i] = 0;
|
|
FMDBG("resurrect xfr %d\n", i);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_q_event
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to queue an event for user.
|
|
|
|
NOTE:
|
|
Applications call the VIDIOC_QBUF ioctl to enqueue an empty (capturing) or
|
|
filled (output) buffer in the driver's incoming queue.
|
|
|
|
Pleaes refer tavarua_probe where we register different ioctl's for FM.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param event: event to be queued.
|
|
|
|
@return None.
|
|
*/
|
|
static void tavarua_q_event(struct tavarua_device *radio,
|
|
enum tavarua_evt_t event)
|
|
{
|
|
|
|
struct kfifo *data_b = &radio->data_buf[TAVARUA_BUF_EVENTS];
|
|
unsigned char evt = event;
|
|
FMDBG("updating event_q with event %x\n", event);
|
|
if (kfifo_in_locked(data_b, &evt, 1, &radio->buf_lock[TAVARUA_BUF_EVENTS]))
|
|
wake_up_interruptible(&radio->event_queue);
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_start_xfr
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to process interrupts which require multiple XFR
|
|
operations (RDS search, RDS PS, RDS RT, etc). if any XFR operation is
|
|
already in progress we store information about pending interrupt, which
|
|
will be processed in future when current pending operation is done.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param pending_id: XFR operation (which requires multiple XFR operations in
|
|
steps) to start.
|
|
@param xfr_id: XFR mode to write in XFRCTRL register.
|
|
|
|
@return None.
|
|
*/
|
|
static void tavarua_start_xfr(struct tavarua_device *radio,
|
|
enum tavarua_xfr_t pending_id, enum tavarua_xfr_ctrl_t xfr_id)
|
|
{
|
|
if (radio->xfr_in_progress)
|
|
radio->pending_xfrs[pending_id] = 1;
|
|
else {
|
|
radio->xfr_in_progress = 1;
|
|
request_read_xfr(radio, xfr_id);
|
|
}
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_handle_interrupts
|
|
=============================================================================*/
|
|
/**
|
|
This function processes the interrupts.
|
|
|
|
NOTE:
|
|
tavarua_q_event is used to queue events in App buffer. i.e. App calls the
|
|
VIDIOC_QBUF ioctl to enqueue an empty (capturing) buffer, which is filled
|
|
by tavarua_q_event call.
|
|
|
|
Any async event that requires multiple steps, i.e. search, RT, PS, etc is
|
|
handled one at a time. (We preserve other interrupts when processing one).
|
|
Sync interrupts are given priority.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return None.
|
|
*/
|
|
static void tavarua_handle_interrupts(struct tavarua_device *radio)
|
|
{
|
|
int i;
|
|
int retval, adj_channel_tune_req = 0;
|
|
unsigned char xfr_status;
|
|
if (!radio->handle_irq) {
|
|
FMDBG("IRQ happend, but I wont handle it\n");
|
|
return;
|
|
}
|
|
mutex_lock(&radio->lock);
|
|
retval = tavarua_read_registers(radio, STATUS_REG1, STATUS_REG_NUM);
|
|
if (retval < 0) {
|
|
FMDERR("Fails to read status register and try once again");
|
|
msleep(TAVARUA_DELAY);
|
|
retval = tavarua_read_registers(radio, STATUS_REG1,
|
|
STATUS_REG_NUM);
|
|
if (retval < 0)
|
|
FMDERR("Fails to read status register");
|
|
}
|
|
|
|
FMDBG("INTSTAT1 <%x>\n", radio->registers[STATUS_REG1]);
|
|
FMDBG("INTSTAT2 <%x>\n", radio->registers[STATUS_REG2]);
|
|
FMDBG("INTSTAT3 <%x>\n", radio->registers[STATUS_REG3]);
|
|
|
|
if (radio->registers[STATUS_REG1] & READY) {
|
|
complete(&radio->sync_req_done);
|
|
tavarua_q_event(radio, TAVARUA_EVT_RADIO_READY);
|
|
}
|
|
|
|
/* Tune completed */
|
|
if (radio->registers[STATUS_REG1] & TUNE) {
|
|
if (radio->tune_req) {
|
|
complete(&radio->sync_req_done);
|
|
radio->tune_req = 0;
|
|
}
|
|
|
|
/*
|
|
* Do not queue the TUNE event while validating if the station
|
|
* is good or not. As part of channel validation we tune to the
|
|
* adjacent station, measure its MPX_DCC value, then tune back
|
|
* to the original station and measure its MPX_DCC value.
|
|
* Compare the MPX_DCC values of curent and adjacent stations
|
|
* and decide if the channel is valid or not. During this period
|
|
* we should not queue the TUNE event to the upper layers.
|
|
*/
|
|
adj_channel_tune_req = atomic_read(&radio->validate_channel);
|
|
if (adj_channel_tune_req) {
|
|
complete(&radio->sync_req_done);
|
|
FMDBG("Tune event for adjacent channel\n");
|
|
} else {
|
|
tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC);
|
|
FMDBG("Queueing Tune event\n");
|
|
}
|
|
|
|
if (radio->srch_params.get_list) {
|
|
tavarua_start_xfr(radio, TAVARUA_XFR_SRCH_LIST,
|
|
RX_STATIONS_0);
|
|
}
|
|
radio->srch_params.get_list = 0;
|
|
radio->xfr_in_progress = 0;
|
|
radio->xfr_bytes_left = 0;
|
|
for (i = 0; i < TAVARUA_BUF_MAX; i++) {
|
|
if (i >= TAVARUA_BUF_RT_RDS)
|
|
kfifo_reset(&radio->data_buf[i]);
|
|
}
|
|
for (i = 0; i < TAVARUA_XFR_MAX; i++) {
|
|
if (i >= TAVARUA_XFR_RT_RDS)
|
|
radio->pending_xfrs[i] = 0;
|
|
}
|
|
retval = tavarua_read_registers(radio, TUNECTRL, 1);
|
|
/* send to user station parameters */
|
|
if (retval > -1) {
|
|
/* Signal strength */
|
|
if (!(radio->registers[TUNECTRL] & SIGSTATE))
|
|
tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH);
|
|
else
|
|
tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH);
|
|
/* mono/stereo */
|
|
if ((radio->registers[TUNECTRL] & MOSTSTATE))
|
|
tavarua_q_event(radio, TAVARUA_EVT_STEREO);
|
|
else
|
|
tavarua_q_event(radio, TAVARUA_EVT_MONO);
|
|
/* is RDS available */
|
|
if ((radio->registers[TUNECTRL] & RDSSYNC))
|
|
tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL);
|
|
else
|
|
tavarua_q_event(radio,
|
|
TAVARUA_EVT_RDS_NOT_AVAIL);
|
|
}
|
|
|
|
} else {
|
|
if (radio->tune_req) {
|
|
FMDERR("Tune INT is pending\n");
|
|
mutex_unlock(&radio->lock);
|
|
return;
|
|
}
|
|
}
|
|
/* Search completed (read FREQ) */
|
|
if (radio->registers[STATUS_REG1] & SEARCH)
|
|
tavarua_q_event(radio, TAVARUA_EVT_SEEK_COMPLETE);
|
|
|
|
/* Scanning for next station */
|
|
if (radio->registers[STATUS_REG1] & SCANNEXT)
|
|
tavarua_q_event(radio, TAVARUA_EVT_SCAN_NEXT);
|
|
|
|
/* Signal indicator change (read SIGSTATE) */
|
|
if (radio->registers[STATUS_REG1] & SIGNAL) {
|
|
retval = tavarua_read_registers(radio, TUNECTRL, 1);
|
|
if (retval > -1) {
|
|
if (!(radio->registers[TUNECTRL] & SIGSTATE))
|
|
tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH);
|
|
else
|
|
tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH);
|
|
}
|
|
}
|
|
|
|
/* RDS synchronization state change (read RDSSYNC) */
|
|
if (radio->registers[STATUS_REG1] & SYNC) {
|
|
retval = tavarua_read_registers(radio, TUNECTRL, 1);
|
|
if (retval > -1) {
|
|
if ((radio->registers[TUNECTRL] & RDSSYNC))
|
|
tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL);
|
|
else
|
|
tavarua_q_event(radio,
|
|
TAVARUA_EVT_RDS_NOT_AVAIL);
|
|
}
|
|
}
|
|
|
|
/* Audio Control indicator (read AUDIOIND) */
|
|
if (radio->registers[STATUS_REG1] & AUDIO) {
|
|
retval = tavarua_read_registers(radio, AUDIOIND, 1);
|
|
if (retval > -1) {
|
|
if ((radio->registers[AUDIOIND] & 0x01))
|
|
tavarua_q_event(radio, TAVARUA_EVT_STEREO);
|
|
else
|
|
tavarua_q_event(radio, TAVARUA_EVT_MONO);
|
|
}
|
|
}
|
|
|
|
/* interrupt register 2 */
|
|
|
|
/* New unread RDS data group available */
|
|
if (radio->registers[STATUS_REG2] & RDSDAT) {
|
|
FMDBG("Raw RDS Available\n");
|
|
tavarua_rds_read(radio);
|
|
tavarua_q_event(radio, TAVARUA_EVT_NEW_RAW_RDS);
|
|
}
|
|
|
|
/* New RDS Program Service Table available */
|
|
if (radio->registers[STATUS_REG2] & RDSPS) {
|
|
FMDBG("New PS RDS\n");
|
|
tavarua_start_xfr(radio, TAVARUA_XFR_PS_RDS, RDS_PS_0);
|
|
}
|
|
|
|
/* New RDS Radio Text available */
|
|
if (radio->registers[STATUS_REG2] & RDSRT) {
|
|
FMDBG("New RT RDS\n");
|
|
tavarua_start_xfr(radio, TAVARUA_XFR_RT_RDS, RDS_RT_0);
|
|
}
|
|
|
|
/* New RDS Radio Text available */
|
|
if (radio->registers[STATUS_REG2] & RDSAF) {
|
|
FMDBG("New AF RDS\n");
|
|
tavarua_start_xfr(radio, TAVARUA_XFR_AF_LIST, RDS_AF_0);
|
|
}
|
|
/* Trasmitter an RDS Group */
|
|
if (radio->registers[STATUS_REG2] & TXRDSDAT) {
|
|
FMDBG("New TXRDSDAT\n");
|
|
tavarua_q_event(radio, TAVARUA_EVT_TXRDSDAT);
|
|
}
|
|
|
|
/* Complete RDS buffer is available for transmission */
|
|
if (radio->registers[STATUS_REG2] & TXRDSDONE) {
|
|
FMDBG("New TXRDSDAT\n");
|
|
tavarua_q_event(radio, TAVARUA_EVT_TXRDSDONE);
|
|
}
|
|
/* interrupt register 3 */
|
|
|
|
/* Data transfer (XFR) completed */
|
|
if (radio->registers[STATUS_REG3] & TRANSFER) {
|
|
FMDBG("XFR Interrupt\n");
|
|
tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1);
|
|
FMDBG("XFRCTRL IS: %x\n", radio->registers[XFRCTRL]);
|
|
xfr_status = radio->registers[XFRCTRL];
|
|
switch (xfr_status) {
|
|
case RDS_PS_0:
|
|
FMDBG("PS Header\n");
|
|
copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, 5);
|
|
radio->xfr_bytes_left = (radio->registers[XFRCTRL+1] &
|
|
0x0F) * 8;
|
|
FMDBG("PS RDS Length: %d\n", radio->xfr_bytes_left);
|
|
if ((radio->xfr_bytes_left > 0) &&
|
|
(radio->xfr_bytes_left < 97))
|
|
request_read_xfr(radio, RDS_PS_1);
|
|
else
|
|
radio->xfr_in_progress = 0;
|
|
break;
|
|
case RDS_PS_1:
|
|
case RDS_PS_2:
|
|
case RDS_PS_3:
|
|
case RDS_PS_4:
|
|
case RDS_PS_5:
|
|
case RDS_PS_6:
|
|
FMDBG("PS Data\n");
|
|
copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, XFR_REG_NUM);
|
|
radio->xfr_bytes_left -= XFR_REG_NUM;
|
|
if (radio->xfr_bytes_left > 0) {
|
|
if ((xfr_status + 1) > RDS_PS_6)
|
|
request_read_xfr(radio, RDS_PS_6);
|
|
else
|
|
request_read_xfr(radio, xfr_status+1);
|
|
} else {
|
|
radio->xfr_in_progress = 0;
|
|
tavarua_q_event(radio, TAVARUA_EVT_NEW_PS_RDS);
|
|
}
|
|
break;
|
|
case RDS_RT_0:
|
|
FMDBG("RT Header\n");
|
|
copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, 5);
|
|
radio->xfr_bytes_left = radio->registers[XFRCTRL+1]
|
|
& 0x7F;
|
|
FMDBG("RT RDS Length: %d\n", radio->xfr_bytes_left);
|
|
/*RT_1 to RT_4 16 byte registers so 64 bytes */
|
|
if ((radio->xfr_bytes_left > 0)
|
|
&& (radio->xfr_bytes_left < 65))
|
|
request_read_xfr(radio, RDS_RT_1);
|
|
break;
|
|
case RDS_RT_1:
|
|
case RDS_RT_2:
|
|
case RDS_RT_3:
|
|
case RDS_RT_4:
|
|
FMDBG("xfr interrupt RT data\n");
|
|
copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, XFR_REG_NUM);
|
|
radio->xfr_bytes_left -= XFR_REG_NUM;
|
|
if (radio->xfr_bytes_left > 0)
|
|
request_read_xfr(radio, xfr_status+1);
|
|
else {
|
|
radio->xfr_in_progress = 0;
|
|
tavarua_q_event(radio, TAVARUA_EVT_NEW_RT_RDS);
|
|
}
|
|
break;
|
|
case RDS_AF_0:
|
|
copy_from_xfr(radio, TAVARUA_BUF_AF_LIST,
|
|
XFR_REG_NUM);
|
|
radio->xfr_bytes_left = radio->registers[XFRCTRL+5]-11;
|
|
if (radio->xfr_bytes_left > 0)
|
|
request_read_xfr(radio, RDS_AF_1);
|
|
else
|
|
radio->xfr_in_progress = 0;
|
|
break;
|
|
case RDS_AF_1:
|
|
copy_from_xfr(radio, TAVARUA_BUF_AF_LIST,
|
|
radio->xfr_bytes_left);
|
|
tavarua_q_event(radio, TAVARUA_EVT_NEW_AF_LIST);
|
|
radio->xfr_in_progress = 0;
|
|
break;
|
|
case RX_CONFIG:
|
|
case RADIO_CONFIG:
|
|
case RDS_CONFIG:
|
|
memcpy(radio->sync_xfr_regs,
|
|
&radio->registers[XFRCTRL+1], XFR_REG_NUM);
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case RX_STATIONS_0:
|
|
FMDBG("Search list has %d stations\n",
|
|
radio->registers[XFRCTRL+1]);
|
|
radio->xfr_bytes_left = radio->registers[XFRCTRL+1]*2;
|
|
if (!radio->registers[XFRCTRL+1]) {
|
|
copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
|
|
1);
|
|
tavarua_q_event(radio,
|
|
TAVARUA_EVT_NEW_SRCH_LIST);
|
|
radio->xfr_in_progress = 0;
|
|
} else if (radio->xfr_bytes_left > 14) {
|
|
copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
|
|
RX_STATIONS0_LEN);
|
|
request_read_xfr(radio, RX_STATIONS_1);
|
|
} else if (radio->xfr_bytes_left) {
|
|
FMDBG("In else RX_STATIONS_0\n");
|
|
copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
|
|
radio->xfr_bytes_left+1);
|
|
tavarua_q_event(radio,
|
|
TAVARUA_EVT_NEW_SRCH_LIST);
|
|
radio->xfr_in_progress = 0;
|
|
}
|
|
break;
|
|
case RX_STATIONS_1:
|
|
FMDBG("In RX_STATIONS_1");
|
|
copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
|
|
radio->xfr_bytes_left);
|
|
tavarua_q_event(radio, TAVARUA_EVT_NEW_SRCH_LIST);
|
|
radio->xfr_in_progress = 0;
|
|
break;
|
|
case PHY_TXGAIN:
|
|
FMDBG("read PHY_TXGAIN is successful");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (XFR_EXT | 0x80):
|
|
FMDBG("Set tone generator successful\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (0x80 | RX_CONFIG):
|
|
case (0x80 | RADIO_CONFIG):
|
|
case (0x80 | RDS_CONFIG):
|
|
case (0x80 | INT_CTRL):
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (0x80 | RDS_RT_0):
|
|
FMDBG("RT Header Sent\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (0x80 | RDS_RT_1):
|
|
case (0x80 | RDS_RT_2):
|
|
case (0x80 | RDS_RT_3):
|
|
case (0x80 | RDS_RT_4):
|
|
FMDBG("xfr interrupt RT data Sent\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
/*TX Specific transfer */
|
|
case (0x80 | RDS_PS_0):
|
|
FMDBG("PS Header Sent\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (0x80 | RDS_PS_1):
|
|
case (0x80 | RDS_PS_2):
|
|
case (0x80 | RDS_PS_3):
|
|
case (0x80 | RDS_PS_4):
|
|
case (0x80 | RDS_PS_5):
|
|
case (0x80 | RDS_PS_6):
|
|
FMDBG("xfr interrupt PS data Sent\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (0x80 | PHY_TXGAIN):
|
|
FMDBG("write PHY_TXGAIN is successful");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
|
|
case (XFR_POKE_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
|
|
case (XFR_PEEK_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
|
|
FMDBG("XFR interrupt for PEEK/POKE complete\n");
|
|
complete(&radio->sync_req_done);
|
|
break;
|
|
default:
|
|
FMDERR("UNKNOWN XFR = %d\n", xfr_status);
|
|
}
|
|
if (!radio->xfr_in_progress)
|
|
start_pending_xfr(radio);
|
|
|
|
}
|
|
|
|
/* Error occurred. Read ERRCODE to determine cause */
|
|
if (radio->registers[STATUS_REG3] & ERROR) {
|
|
#ifdef FM_DEBUG
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
int retval = sync_read_xfr(radio, ERROR_CODE, xfr_buf);
|
|
FMDBG("retval of ERROR_CODE read : %d\n", retval);
|
|
#endif
|
|
FMDERR("ERROR STATE\n");
|
|
}
|
|
|
|
mutex_unlock(&radio->lock);
|
|
FMDBG("Work is done\n");
|
|
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: read_int_stat
|
|
=============================================================================*/
|
|
/**
|
|
This function is scheduled whenever there is an interrupt pending in interrupt
|
|
queue. i.e. kfmradio.
|
|
|
|
Whenever there is a GPIO interrupt, a delayed work will be queued in to the
|
|
'kfmradio' work queue. Upon execution of this work in the queue, a a call
|
|
to read_int_stat function will be made , which would in turn handle the
|
|
interrupts by reading the INTSTATx registers.
|
|
NOTE:
|
|
Tasks to be run out of a workqueue need to be packaged in a struct
|
|
work_struct structure.
|
|
|
|
@param work: work_struct structure.
|
|
|
|
@return None.
|
|
*/
|
|
static void read_int_stat(struct work_struct *work)
|
|
{
|
|
struct tavarua_device *radio = container_of(work,
|
|
struct tavarua_device, work.work);
|
|
tavarua_handle_interrupts(radio);
|
|
}
|
|
|
|
static void fm_shutdown(struct work_struct *work)
|
|
{
|
|
struct tavarua_device *radio = container_of(work,
|
|
struct tavarua_device, work.work);
|
|
FMDERR("%s: Releasing the FM I2S GPIO\n", __func__);
|
|
if (radio->pdata->config_i2s_gpio != NULL)
|
|
radio->pdata->config_i2s_gpio(FM_I2S_OFF);
|
|
FMDERR("%s: Shutting down FM SOC\n", __func__);
|
|
radio->pdata->fm_shutdown(radio->pdata);
|
|
complete(&radio->shutdown_done);
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
* irq helper functions
|
|
************************************************************************/
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_request_irq
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to acquire a FM GPIO and enable FM interrupts.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return 0 if success else otherwise.
|
|
*/
|
|
static int tavarua_request_irq(struct tavarua_device *radio)
|
|
{
|
|
int retval;
|
|
int irq = radio->pdata->irq;
|
|
if (radio == NULL)
|
|
return -EINVAL;
|
|
|
|
/* A workqueue created with create_workqueue() will have one worker thread
|
|
* for each CPU on the system; create_singlethread_workqueue(), instead,
|
|
* creates a workqueue with a single worker process. The name of the queue
|
|
* is limited to ten characters; it is only used for generating the "command"
|
|
* for the kernel thread(s) (which can be seen in ps or top).
|
|
*/
|
|
/* allocate an interrupt line */
|
|
/* On success, request_irq() returns 0 if everything goes as
|
|
planned. Your interrupt handler will start receiving its
|
|
interrupts immediately. On failure, request_irq()
|
|
returns:
|
|
-EINVAL
|
|
The IRQ number you requested was either
|
|
invalid or reserved, or your passed a NULL
|
|
pointer for the handler() parameter.
|
|
|
|
-EBUSY The IRQ you requested is already being
|
|
handled, and the IRQ cannot be shared.
|
|
|
|
-ENXIO The m68k returns this value for an invalid
|
|
IRQ number.
|
|
*/
|
|
/* Use request_any_context_irq, So that it might work for nested or
|
|
nested interrupts. in MSM8x60, FM is connected to PMIC GPIO and it
|
|
is a nested interrupt*/
|
|
retval = request_any_context_irq(irq, tavarua_isr,
|
|
IRQ_TYPE_EDGE_FALLING, "fm interrupt", radio);
|
|
if (retval < 0) {
|
|
FMDERR("Couldn't acquire FM gpio %d\n", irq);
|
|
return retval;
|
|
} else {
|
|
FMDBG("FM GPIO %d registered\n", irq);
|
|
}
|
|
retval = enable_irq_wake(irq);
|
|
if (retval < 0) {
|
|
FMDERR("Could not enable FM interrupt\n ");
|
|
free_irq(irq , radio);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_disable_irq
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to disable FM irq and free up FM interrupt handling
|
|
resources.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return 0 if success else otherwise.
|
|
*/
|
|
static int tavarua_disable_irq(struct tavarua_device *radio)
|
|
{
|
|
int irq;
|
|
if (!radio)
|
|
return -EINVAL;
|
|
irq = radio->pdata->irq;
|
|
disable_irq_wake(irq);
|
|
free_irq(irq, radio);
|
|
cancel_delayed_work_sync(&radio->work);
|
|
flush_workqueue(radio->wqueue);
|
|
return 0;
|
|
}
|
|
|
|
static int optimized_search_algorithm(struct tavarua_device *radio,
|
|
int region)
|
|
{
|
|
unsigned char adie_type_bahma;
|
|
int retval = 0;
|
|
unsigned int rdsMask = 0;
|
|
unsigned char value = 0;
|
|
|
|
adie_type_bahma = is_bahama();
|
|
|
|
switch (region) {
|
|
case TAVARUA_REGION_US:
|
|
/*
|
|
Radio band for all the 200KHz channel-spaced regions
|
|
coming under EUROPE too, have been set as TAVARUA_REGION_US.
|
|
*/
|
|
FMDBG("%s: The region selected from APP is"
|
|
" : TAVARUA_REGION_US", __func__);
|
|
break;
|
|
case TAVARUA_REGION_EU:
|
|
/*
|
|
Radio band for all the 50KHz channel-spaced regions
|
|
coming under EUROPE, have been set as TAVARUA_REGION_EU.
|
|
*/
|
|
FMDBG("%s: The region selected from APP is : "
|
|
"TAVARUA_REGION_EU", __func__);
|
|
break;
|
|
case TAVARUA_REGION_JAPAN:
|
|
/*
|
|
Radio band for the 100KHz channel-spaced JAPAN region
|
|
has been set as TAVARUA_REGION_JAPAN.
|
|
*/
|
|
FMDBG("%s: The region selected from APP is"
|
|
" : TAVARUA_REGION_JAPAN", __func__);
|
|
break;
|
|
case TAVARUA_REGION_JAPAN_WIDE:
|
|
/*
|
|
Radio band for the 50KHz channel-spaced JAPAN WIDE region
|
|
has been set as TAVARUA_REGION_JAPAN_WIDE.
|
|
*/
|
|
FMDBG("%s: The region selected from APP is"
|
|
" : TAVARUA_REGION_JAPAN_WIDE", __func__);
|
|
break;
|
|
case TAVARUA_REGION_OTHER:
|
|
/*
|
|
Radio band for all the 100KHz channel-spaced regions
|
|
including those coming under EUROPE have been set as
|
|
TAVARUA_REGION_OTHER.
|
|
*/
|
|
FMDBG("%s: The region selected from APP is"
|
|
" : TAVARUA_REGION_OTHER", __func__);
|
|
break;
|
|
default:
|
|
pr_err("%s: Should not reach here.", __func__);
|
|
break;
|
|
|
|
}
|
|
|
|
/* Enable or Disable the 200KHz enforcer */
|
|
switch (region) {
|
|
case TAVARUA_REGION_US:
|
|
case TAVARUA_REGION_JAPAN:
|
|
case TAVARUA_REGION_OTHER:
|
|
/*
|
|
These are the 3 bands for which we need to enable the
|
|
200KHz enforcer in ADVCTL reg.
|
|
*/
|
|
if (adie_type_bahma) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
FMDBG("%s: Enabling the 200KHz enforcer for"
|
|
" Region : %d", __func__, region);
|
|
/*Enable the 200KHz enforcer*/
|
|
retval = tavarua_read_registers(radio,
|
|
ADVCTRL, 1);
|
|
if (retval >= 0) {
|
|
rdsMask = radio->registers[ADVCTRL];
|
|
SET_REG_FIELD(rdsMask, ENF_SRCH200khz,
|
|
SRCH200KHZ_OFFSET, SRCH_MASK);
|
|
retval = tavarua_write_register(radio,
|
|
ADVCTRL, rdsMask);
|
|
} else
|
|
return retval;
|
|
} /* if Marimba do nothing */
|
|
break;
|
|
case TAVARUA_REGION_EU:
|
|
case TAVARUA_REGION_JAPAN_WIDE:
|
|
/*
|
|
These are the 2 bands for which we need to disable the
|
|
200KHz enforcer in ADVCTL reg.
|
|
Radio band for all the 50KHz channel-spaced regions
|
|
coming under EUROPE have been set as TAVARUA_REGION_EU.
|
|
*/
|
|
if (adie_type_bahma) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
FMDBG("%s: Disabling the 200KHz enforcer for"
|
|
" Region : %d", __func__, region);
|
|
/*
|
|
Disable 200KHz enforcer for all 50 KHz
|
|
spaced regions.
|
|
*/
|
|
retval = tavarua_read_registers(radio,
|
|
ADVCTRL, 1);
|
|
if (retval >= 0) {
|
|
rdsMask = radio->registers[ADVCTRL];
|
|
SET_REG_FIELD(rdsMask, NO_SRCH200khz,
|
|
SRCH200KHZ_OFFSET, SRCH_MASK);
|
|
retval = tavarua_write_register(radio,
|
|
ADVCTRL, rdsMask);
|
|
} else
|
|
return retval;
|
|
} /* if Marimba do nothing */
|
|
break;
|
|
default:
|
|
FMDBG("%s: Defaulting in case of Enabling/Disabling"
|
|
"the 200KHz Enforcer", __func__);
|
|
break;
|
|
}
|
|
|
|
/* Set channel spacing */
|
|
switch (region) {
|
|
case TAVARUA_REGION_US:
|
|
if ((adie_type_bahma) && (bahama_version == 0x09)) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
/*
|
|
Configuring all 200KHZ spaced regions as 100KHz due to
|
|
change in the new Bahma FM SoC search algorithm.
|
|
*/
|
|
value = FM_CH_SPACE_100KHZ;
|
|
} else if ((adie_type_bahma) && (bahama_version == 0x0a)) {
|
|
FMDBG("Adie type : Bahama B1\n");
|
|
value = FM_CH_SPACE_200KHZ;
|
|
} else {
|
|
FMDBG("Adie type : Marimba\n");
|
|
value = FM_CH_SPACE_200KHZ;
|
|
}
|
|
break;
|
|
case TAVARUA_REGION_JAPAN:
|
|
case TAVARUA_REGION_OTHER:
|
|
if ((adie_type_bahma) && (bahama_version == 0x09)) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
FMDBG("%s: Configuring the channel-spacing as 50KHz"
|
|
"for the Region : %d", __func__, region);
|
|
/*
|
|
Configuring all 100KHZ spaced regions as 50KHz due to
|
|
change in the new Bahma FM SoC search algorithm.
|
|
*/
|
|
value = FM_CH_SPACE_50KHZ;
|
|
} else if ((adie_type_bahma) && (bahama_version == 0x0a)) {
|
|
FMDBG("Adie type : Bahama B1\n");
|
|
value = FM_CH_SPACE_100KHZ;
|
|
} else {
|
|
FMDBG("Adie type : Marimba\n");
|
|
value = FM_CH_SPACE_100KHZ;
|
|
}
|
|
break;
|
|
case TAVARUA_REGION_EU:
|
|
case TAVARUA_REGION_JAPAN_WIDE:
|
|
value = FM_CH_SPACE_50KHZ;
|
|
break;
|
|
default:
|
|
FMDBG("%s: Defualting in case of Channel-Spacing", __func__);
|
|
break;
|
|
}
|
|
|
|
SET_REG_FIELD(radio->registers[RDCTRL], value,
|
|
RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK);
|
|
|
|
return retval;
|
|
}
|
|
/*************************************************************************
|
|
* fops/IOCTL helper functions
|
|
************************************************************************/
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_search
|
|
=============================================================================*/
|
|
/**
|
|
This interface sets the search control features.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param on: The value of a control.
|
|
@param dir: FM search direction.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_search(struct tavarua_device *radio, int on, int dir)
|
|
{
|
|
enum search_t srch = radio->registers[SRCHCTRL] & SRCH_MODE;
|
|
|
|
FMDBG("In tavarua_search\n");
|
|
if (on) {
|
|
radio->registers[SRCHRDS1] = 0x00;
|
|
radio->registers[SRCHRDS2] = 0x00;
|
|
/* Set freq band */
|
|
switch (srch) {
|
|
case SCAN_FOR_STRONG:
|
|
case SCAN_FOR_WEAK:
|
|
radio->srch_params.get_list = 1;
|
|
radio->registers[SRCHRDS2] =
|
|
radio->srch_params.preset_num;
|
|
break;
|
|
case RDS_SEEK_PTY:
|
|
case RDS_SCAN_PTY:
|
|
radio->registers[SRCHRDS2] =
|
|
radio->srch_params.srch_pty;
|
|
break;
|
|
case RDS_SEEK_PI:
|
|
radio->registers[SRCHRDS1] =
|
|
(radio->srch_params.srch_pi & 0xFF00) >> 8;
|
|
radio->registers[SRCHRDS2] =
|
|
(radio->srch_params.srch_pi & 0x00FF);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
radio->registers[SRCHCTRL] |= SRCH_ON;
|
|
} else {
|
|
radio->registers[SRCHCTRL] &= ~SRCH_ON;
|
|
radio->srch_params.get_list = 0;
|
|
}
|
|
radio->registers[SRCHCTRL] = (dir << 3) |
|
|
(radio->registers[SRCHCTRL] & 0xF7);
|
|
|
|
FMDBG("SRCHCTRL <%x>\n", radio->registers[SRCHCTRL]);
|
|
FMDBG("Search Started\n");
|
|
return tavarua_write_registers(radio, SRCHRDS1,
|
|
&radio->registers[SRCHRDS1], 3);
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_set_region
|
|
=============================================================================*/
|
|
/**
|
|
This interface configures the FM radio.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param req_region: FM band types. These types defines the FM band minimum and
|
|
maximum frequencies in the FM band.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_set_region(struct tavarua_device *radio,
|
|
int req_region)
|
|
{
|
|
int retval = 0;
|
|
unsigned int rdsMask = 0;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
unsigned char value;
|
|
unsigned int spacing = 0.100 * FREQ_MUL;
|
|
unsigned int band_low, band_high;
|
|
unsigned int low_band_limit = 76.0 * FREQ_MUL;
|
|
enum tavarua_region_t region = req_region;
|
|
unsigned char adie_type_bahma;
|
|
|
|
adie_type_bahma = is_bahama();
|
|
|
|
/* Set freq band */
|
|
if (region == TAVARUA_REGION_JAPAN)
|
|
SET_REG_FIELD(radio->registers[RDCTRL], 1,
|
|
RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);
|
|
else
|
|
SET_REG_FIELD(radio->registers[RDCTRL], 0,
|
|
RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);
|
|
|
|
/* Set De-emphasis and soft band range*/
|
|
SET_REG_FIELD(radio->registers[RDCTRL], radio->region_params.emphasis,
|
|
RDCTRL_DEEMPHASIS_OFFSET, RDCTRL_DEEMPHASIS_MASK);
|
|
|
|
/* set RDS standard */
|
|
SET_REG_FIELD(radio->registers[RDSCTRL], radio->region_params.rds_std,
|
|
RDSCTRL_STANDARD_OFFSET, RDSCTRL_STANDARD_MASK);
|
|
|
|
FMDBG("RDSCTRLL %x\n", radio->registers[RDSCTRL]);
|
|
retval = tavarua_write_register(radio, RDSCTRL,
|
|
radio->registers[RDSCTRL]);
|
|
if (retval < 0) {
|
|
FMDERR("Failed to set RDS/RBDS standard\n");
|
|
return retval;
|
|
}
|
|
|
|
/* Set the lower and upper band limits*/
|
|
retval = sync_read_xfr(radio, RADIO_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("failed to get RADIO_CONFIG\n");
|
|
return retval;
|
|
}
|
|
|
|
band_low = (radio->region_params.band_low -
|
|
low_band_limit) / spacing;
|
|
band_high = (radio->region_params.band_high -
|
|
low_band_limit) / spacing;
|
|
|
|
xfr_buf[0] = RSH_DATA(band_low, 8);
|
|
xfr_buf[1] = GET_ABS_VAL(band_low);
|
|
xfr_buf[2] = RSH_DATA(band_high, 8);
|
|
xfr_buf[3] = GET_ABS_VAL(band_high);
|
|
xfr_buf[4] = 0; /* Active LOW */
|
|
retval = sync_write_xfr(radio, RADIO_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("Could not set regional settings\n");
|
|
return retval;
|
|
}
|
|
radio->region_params.region = region;
|
|
|
|
/* Check for the FM Algorithm used */
|
|
if (radio->enable_optimized_srch_alg) {
|
|
FMDBG("Optimized Srch Algorithm!!!");
|
|
optimized_search_algorithm(radio, region);
|
|
} else {
|
|
FMDBG("Native Srch Algorithm!!!");
|
|
/* Enable/Disable the 200KHz enforcer */
|
|
switch (region) {
|
|
case TAVARUA_REGION_US:
|
|
if (adie_type_bahma) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
/*Enable the 200KHz enforcer*/
|
|
retval = tavarua_read_registers(radio,
|
|
ADVCTRL, 1);
|
|
if (retval >= 0) {
|
|
rdsMask = radio->registers[ADVCTRL];
|
|
SET_REG_FIELD(rdsMask, ENF_SRCH200khz,
|
|
SRCH200KHZ_OFFSET, SRCH_MASK);
|
|
retval = tavarua_write_register(radio,
|
|
ADVCTRL, rdsMask);
|
|
} else
|
|
return retval;
|
|
} /* if Marimba do nothing */
|
|
break;
|
|
case TAVARUA_REGION_EU:
|
|
case TAVARUA_REGION_JAPAN:
|
|
case TAVARUA_REGION_JAPAN_WIDE:
|
|
default:
|
|
if (adie_type_bahma) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
/*
|
|
Disable 200KHz enforcer for all 100/50 KHz
|
|
spaced regions.
|
|
*/
|
|
retval = tavarua_read_registers(radio,
|
|
ADVCTRL, 1);
|
|
if (retval >= 0) {
|
|
rdsMask = radio->registers[ADVCTRL];
|
|
SET_REG_FIELD(rdsMask, NO_SRCH200khz,
|
|
SRCH200KHZ_OFFSET, SRCH_MASK);
|
|
retval = tavarua_write_register(radio,
|
|
ADVCTRL, rdsMask);
|
|
} else
|
|
return retval;
|
|
} /* if Marimba do nothing */
|
|
break;
|
|
}
|
|
|
|
/* Set channel spacing */
|
|
if (region == TAVARUA_REGION_US) {
|
|
if (adie_type_bahma) {
|
|
FMDBG("Adie type : Bahama\n");
|
|
/*
|
|
Configuring all 200KHZ spaced regions as
|
|
100KHz due to change in the new Bahma
|
|
FM SoC search algorithm.
|
|
*/
|
|
value = FM_CH_SPACE_100KHZ;
|
|
} else {
|
|
FMDBG("Adie type : Marimba\n");
|
|
value = FM_CH_SPACE_200KHZ;
|
|
}
|
|
} else {
|
|
/*
|
|
Set the channel spacing as configured from
|
|
the upper layers.
|
|
*/
|
|
value = radio->region_params.spacing;
|
|
}
|
|
SET_REG_FIELD(radio->registers[RDCTRL], value,
|
|
RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK);
|
|
|
|
}
|
|
|
|
/* Write the config values into RDCTL register */
|
|
FMDBG("RDCTRL: %x\n", radio->registers[RDCTRL]);
|
|
retval = tavarua_write_register(radio, RDCTRL,
|
|
radio->registers[RDCTRL]);
|
|
if (retval < 0) {
|
|
FMDERR("Could not set region in rdctrl\n");
|
|
return retval;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_get_freq
|
|
=============================================================================*/
|
|
/**
|
|
This interface gets the current frequency.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param freq: struct v4l2_frequency. This will be set to the resultant
|
|
frequency in units of 62.5 kHz on success.
|
|
|
|
NOTE:
|
|
To get the current tuner or modulator radio frequency applications set the
|
|
tuner field of a struct v4l2_frequency to the respective tuner or modulator
|
|
number (only input devices have tuners, only output devices have modulators),
|
|
zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a
|
|
pointer to this structure. The driver stores the current frequency in the
|
|
frequency field.
|
|
|
|
Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner or
|
|
struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set, in
|
|
units of 62.5 Hz.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_get_freq(struct tavarua_device *radio,
|
|
struct v4l2_frequency *freq)
|
|
{
|
|
int retval;
|
|
unsigned short chan;
|
|
unsigned int band_bottom;
|
|
unsigned int spacing;
|
|
band_bottom = radio->region_params.band_low;
|
|
spacing = 0.100 * FREQ_MUL;
|
|
/* read channel */
|
|
retval = tavarua_read_registers(radio, FREQ, 2);
|
|
chan = radio->registers[FREQ];
|
|
|
|
/* Frequency (MHz) = 100 (kHz) x Channel + Bottom of Band (MHz) */
|
|
freq->frequency = spacing * chan + band_bottom;
|
|
if (radio->registers[TUNECTRL] & ADD_OFFSET)
|
|
freq->frequency += 800;
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_set_freq
|
|
=============================================================================*/
|
|
/**
|
|
This interface sets the current frequency.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param freq: desired frequency sent by the client in 62.5 kHz units.
|
|
|
|
NOTE:
|
|
To change the current tuner or modulator radio frequency, applications
|
|
initialize the tuner, type and frequency fields, and the reserved array of a
|
|
struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer to
|
|
this structure. When the requested frequency is not possible the driver
|
|
assumes the closest possible value. However VIDIOC_S_FREQUENCY is a
|
|
write-only ioctl, it does not return the actual new frequency.
|
|
|
|
Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner
|
|
or struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set,
|
|
in units of 62.5 Hz.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_set_freq(struct tavarua_device *radio, unsigned int freq)
|
|
{
|
|
|
|
unsigned int band_bottom;
|
|
unsigned char chan;
|
|
unsigned char cmd[] = {0x00, 0x00};
|
|
unsigned int spacing;
|
|
int retval;
|
|
band_bottom = radio->region_params.band_low;
|
|
spacing = 0.100 * FREQ_MUL;
|
|
if ((freq % 1600) == 800) {
|
|
cmd[1] = ADD_OFFSET;
|
|
freq -= 800;
|
|
}
|
|
/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / 100 (kHz) */
|
|
chan = (freq - band_bottom) / spacing;
|
|
|
|
cmd[0] = chan;
|
|
cmd[1] |= TUNE_STATION;
|
|
radio->tune_req = 1;
|
|
retval = tavarua_write_registers(radio, FREQ, cmd, 2);
|
|
if (retval < 0)
|
|
radio->tune_req = 0;
|
|
return retval;
|
|
|
|
}
|
|
|
|
/**************************************************************************
|
|
* File Operations Interface
|
|
*************************************************************************/
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_fops_read
|
|
=============================================================================*/
|
|
/**
|
|
This function is called when a process, which already opened the dev file,
|
|
attempts to read from it.
|
|
|
|
In case of tavarua driver, it is called to read RDS data.
|
|
|
|
@param file: file descriptor.
|
|
@param buf: The buffer to fill with data.
|
|
@param count: The length of the buffer in bytes.
|
|
@param ppos: Our offset in the file.
|
|
|
|
@return The number of bytes put into the buffer on sucess.
|
|
-EFAULT if there is no access to user buffer
|
|
*/
|
|
static ssize_t tavarua_fops_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS];
|
|
|
|
/* block if no new data available */
|
|
while (!kfifo_len(rds_buf)) {
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return -EWOULDBLOCK;
|
|
if (wait_event_interruptible(radio->read_queue,
|
|
kfifo_len(rds_buf)) < 0)
|
|
return -EINTR;
|
|
}
|
|
|
|
/* calculate block count from byte count */
|
|
count /= BYTES_PER_BLOCK;
|
|
|
|
|
|
/* check if we can write to the user buffer */
|
|
if (!access_ok(VERIFY_WRITE, buf, count*BYTES_PER_BLOCK))
|
|
return -EFAULT;
|
|
|
|
/* copy RDS block out of internal buffer and to user buffer */
|
|
return kfifo_out_locked(rds_buf, buf, count*BYTES_PER_BLOCK,
|
|
&radio->buf_lock[TAVARUA_BUF_RAW_RDS]);
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_fops_write
|
|
=============================================================================*/
|
|
/**
|
|
This function is called when a process, which already opened the dev file,
|
|
attempts to write to it.
|
|
|
|
In case of tavarua driver, it is called to write RDS data to host.
|
|
|
|
@param file: file descriptor.
|
|
@param buf: The buffer which has data to write.
|
|
@param count: The length of the buffer.
|
|
@param ppos: Our offset in the file.
|
|
|
|
@return The number of bytes written from the buffer.
|
|
*/
|
|
static ssize_t tavarua_fops_write(struct file *file, const char __user *data,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval = 0;
|
|
int bytes_to_copy;
|
|
int bytes_copied = 0;
|
|
int bytes_left;
|
|
int chunk_index = 0;
|
|
unsigned char tx_data[XFR_REG_NUM];
|
|
/* Disable TX of this type first */
|
|
switch (radio->tx_mode) {
|
|
case TAVARUA_TX_RT:
|
|
bytes_left = min((int)count, MAX_RT_LENGTH);
|
|
tx_data[1] = 0;
|
|
break;
|
|
case TAVARUA_TX_PS:
|
|
bytes_left = min((int)count, MAX_PS_LENGTH);
|
|
tx_data[4] = 0;
|
|
break;
|
|
default:
|
|
FMDERR("%s: Unknown TX mode\n", __func__);
|
|
return -1;
|
|
}
|
|
retval = sync_write_xfr(radio, radio->tx_mode, tx_data);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
/* send payload to FM hardware */
|
|
while (bytes_left) {
|
|
chunk_index++;
|
|
bytes_to_copy = min(bytes_left, XFR_REG_NUM);
|
|
if (copy_from_user(tx_data, data + bytes_copied, bytes_to_copy))
|
|
return -EFAULT;
|
|
retval = sync_write_xfr(radio, radio->tx_mode +
|
|
chunk_index, tx_data);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
bytes_copied += bytes_to_copy;
|
|
bytes_left -= bytes_to_copy;
|
|
}
|
|
|
|
/* send the header */
|
|
switch (radio->tx_mode) {
|
|
case TAVARUA_TX_RT:
|
|
FMDBG("Writing RT header\n");
|
|
tx_data[0] = bytes_copied;
|
|
tx_data[1] = TX_ON | 0x03; /* on | PTY */
|
|
tx_data[2] = 0x12; /* PI high */
|
|
tx_data[3] = 0x34; /* PI low */
|
|
break;
|
|
case TAVARUA_TX_PS:
|
|
FMDBG("Writing PS header\n");
|
|
tx_data[0] = chunk_index;
|
|
tx_data[1] = 0x03; /* PTY */
|
|
tx_data[2] = 0x12; /* PI high */
|
|
tx_data[3] = 0x34; /* PI low */
|
|
tx_data[4] = TX_ON | 0x01;
|
|
break;
|
|
default:
|
|
FMDERR("%s: Unknown TX mode\n", __func__);
|
|
return -1;
|
|
}
|
|
retval = sync_write_xfr(radio, radio->tx_mode, tx_data);
|
|
if (retval < 0)
|
|
return retval;
|
|
FMDBG("done writing: %d\n", retval);
|
|
return bytes_copied;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_fops_open
|
|
=============================================================================*/
|
|
/**
|
|
This function is called when a process tries to open the device file, like
|
|
"cat /dev/mycharfile"
|
|
|
|
@param file: file descriptor.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_fops_open(struct file *file)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
struct marimba config = { .mod_id = SLAVE_ID_BAHAMA};
|
|
int retval = -ENODEV;
|
|
unsigned char value;
|
|
/* FM core bring up */
|
|
int i = 0;
|
|
char fm_ctl0_part1[] = { 0xCA, 0xCE, 0xD6 };
|
|
char fm_ctl1[] = { 0x03 };
|
|
char fm_ctl0_part2[] = { 0xB6, 0xB7 };
|
|
char buffer[] = {0x00, 0x48, 0x8A, 0x8E, 0x97, 0xB7};
|
|
int bahama_present = -ENODEV;
|
|
|
|
INIT_DELAYED_WORK(&radio->work, read_int_stat);
|
|
if (!atomic_dec_and_test(&radio->users)) {
|
|
pr_err("%s: Device already in use."
|
|
"Try again later", __func__);
|
|
atomic_inc(&radio->users);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* initial gpio pin config & Power up */
|
|
retval = radio->pdata->fm_setup(radio->pdata);
|
|
if (retval) {
|
|
printk(KERN_ERR "%s: failed config gpio & pmic\n", __func__);
|
|
goto open_err_setup;
|
|
}
|
|
if (radio->pdata->config_i2s_gpio != NULL) {
|
|
retval = radio->pdata->config_i2s_gpio(FM_I2S_ON);
|
|
if (retval) {
|
|
printk(KERN_ERR "%s: failed config gpio\n", __func__);
|
|
goto config_i2s_err;
|
|
}
|
|
}
|
|
/* enable irq */
|
|
retval = tavarua_request_irq(radio);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: failed to request irq\n", __func__);
|
|
goto open_err_req_irq;
|
|
}
|
|
/* call top level marimba interface here to enable FM core */
|
|
FMDBG("initializing SoC\n");
|
|
|
|
bahama_present = is_bahama();
|
|
|
|
if (bahama_present == -ENODEV)
|
|
return -ENODEV;
|
|
|
|
if (bahama_present)
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
else
|
|
radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA;
|
|
|
|
value = FM_ENABLE;
|
|
retval = marimba_write_bit_mask(&config,
|
|
MARIMBA_XO_BUFF_CNTRL, &value, 1, value);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n",
|
|
__func__);
|
|
goto open_err_all;
|
|
}
|
|
|
|
|
|
/* Bring up FM core */
|
|
if (bahama_present) {
|
|
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
/* Read the Bahama version*/
|
|
retval = marimba_read_bit_mask(&config,
|
|
0x00, &bahama_version, 1, 0x1F);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: version read failed",
|
|
__func__);
|
|
goto open_err_all;
|
|
}
|
|
|
|
/* Check for Bahama V2 variant*/
|
|
if ((bahama_version == 0x09) || (bahama_version == 0x0a)) {
|
|
|
|
/* In case of Bahama v2, forcefully enable the
|
|
* internal analog and digital voltage controllers
|
|
*/
|
|
value = 0x06;
|
|
/* value itself used as mask in these writes*/
|
|
retval = marimba_write_bit_mask(&config,
|
|
BAHAMA_LDO_DREG_CTL0, &value, 1, value);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:0xF0 write failed\n",
|
|
__func__);
|
|
goto open_err_all;
|
|
}
|
|
value = 0x86;
|
|
retval = marimba_write_bit_mask(&config,
|
|
BAHAMA_LDO_AREG_CTL0, &value, 1, value);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:0xF4 write failed\n",
|
|
__func__);
|
|
goto open_err_all;
|
|
}
|
|
}
|
|
|
|
/*write FM mode*/
|
|
retval = tavarua_write_register(radio, BAHAMA_FM_MODE_REG,
|
|
BAHAMA_FM_MODE_NORMAL);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "failed to set the FM mode: %d\n",
|
|
retval);
|
|
goto open_err_all;
|
|
}
|
|
/*Write first sequence of bytes to FM_CTL0*/
|
|
for (i = 0; i < 3; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL0:set-1 failure: %d\n",
|
|
retval);
|
|
goto open_err_all;
|
|
}
|
|
}
|
|
/*Write the FM_CTL1 sequence*/
|
|
for (i = 0; i < 1; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL1_REG, fm_ctl1[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL1 write failure: %d\n",
|
|
retval);
|
|
goto open_err_all;
|
|
}
|
|
}
|
|
/*Write second sequence of bytes to FM_CTL0*/
|
|
for (i = 0; i < 2; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL0:set-2 failure: %d\n",
|
|
retval);
|
|
goto open_err_all;
|
|
}
|
|
}
|
|
} else {
|
|
retval = tavarua_write_registers(radio, LEAKAGE_CNTRL,
|
|
buffer, 6);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: failed to bring up FM Core\n",
|
|
__func__);
|
|
goto open_err_all;
|
|
}
|
|
}
|
|
/* Wait for interrupt i.e. complete(&radio->sync_req_done); call */
|
|
/*Initialize the completion variable for
|
|
for the proper behavior*/
|
|
init_completion(&radio->sync_req_done);
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout))) {
|
|
retval = -1;
|
|
FMDERR("Timeout waiting for initialization\n");
|
|
}
|
|
|
|
/* get Chip ID */
|
|
retval = tavarua_write_register(radio, XFRCTRL, CHIPID);
|
|
if (retval < 0)
|
|
goto open_err_all;
|
|
msleep(TAVARUA_DELAY);
|
|
tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1);
|
|
if (radio->registers[XFRCTRL] != CHIPID)
|
|
goto open_err_all;
|
|
|
|
radio->chipID = (radio->registers[XFRCTRL+2] << 24) |
|
|
(radio->registers[XFRCTRL+5] << 16) |
|
|
(radio->registers[XFRCTRL+6] << 8) |
|
|
(radio->registers[XFRCTRL+7]);
|
|
|
|
printk(KERN_WARNING DRIVER_NAME ": Chip ID %x\n", radio->chipID);
|
|
if (radio->chipID == MARIMBA_A0) {
|
|
printk(KERN_WARNING DRIVER_NAME ": Unsupported hardware: %x\n",
|
|
radio->chipID);
|
|
retval = -1;
|
|
goto open_err_all;
|
|
}
|
|
|
|
radio->handle_irq = 0;
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
marimba_set_fm_status(radio->marimba, true);
|
|
return 0;
|
|
|
|
|
|
open_err_all:
|
|
/*Disable FM in case of error*/
|
|
value = 0x00;
|
|
marimba_write_bit_mask(&config, MARIMBA_XO_BUFF_CNTRL,
|
|
&value, 1, value);
|
|
tavarua_disable_irq(radio);
|
|
open_err_req_irq:
|
|
if (radio->pdata->config_i2s_gpio != NULL)
|
|
radio->pdata->config_i2s_gpio(FM_I2S_OFF);
|
|
config_i2s_err:
|
|
radio->pdata->fm_shutdown(radio->pdata);
|
|
open_err_setup:
|
|
radio->handle_irq = 1;
|
|
atomic_inc(&radio->users);
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_fops_release
|
|
=============================================================================*/
|
|
/**
|
|
This function is called when a process closes the device file.
|
|
|
|
@param file: file descriptor.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_fops_release(struct file *file)
|
|
{
|
|
int retval;
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
struct marimba config = { .mod_id = SLAVE_ID_BAHAMA};
|
|
unsigned char value;
|
|
int i = 0;
|
|
/*FM Core shutdown sequence for Bahama*/
|
|
char fm_ctl0_part1[] = { 0xB7 };
|
|
char fm_ctl1[] = { 0x03 };
|
|
char fm_ctl0_part2[] = { 0x9F, 0x48, 0x02 };
|
|
int bahama_present = -ENODEV;
|
|
/*FM Core shutdown sequence for Marimba*/
|
|
char buffer[] = {0x18, 0xB7, 0x48};
|
|
bool bt_status = false;
|
|
int index;
|
|
/* internal regulator controllers DREG_CTL0, AREG_CTL0
|
|
* has to be kept in the valid state based on the bt status.
|
|
* 1st row is the state when no clients are active,
|
|
* and the second when bt is in on state.
|
|
*/
|
|
char internal_vreg_ctl[2][2] = {
|
|
{ 0x04, 0x84 },
|
|
{ 0x00, 0x80 }
|
|
};
|
|
|
|
if (!radio) {
|
|
pr_err("%s: Radio device not available...", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
FMDBG("In %s", __func__);
|
|
|
|
FMDBG("%s, Disabling the IRQs\n", __func__);
|
|
/* disable irq */
|
|
retval = tavarua_disable_irq(radio);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: failed to disable irq\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
/* disable radio ctrl */
|
|
retval = tavarua_write_register(radio, RDCTRL, 0x00);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: failed to disable FM\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
init_completion(&radio->shutdown_done);
|
|
|
|
bahama_present = is_bahama();
|
|
|
|
if (bahama_present == -ENODEV)
|
|
return -ENODEV;
|
|
|
|
INIT_DELAYED_WORK(&radio->work, fm_shutdown);
|
|
|
|
if (bahama_present) {
|
|
/*Write first sequence of bytes to FM_CTL0*/
|
|
for (i = 0; i < 1; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL0:Set-1 failure: %d\n",
|
|
retval);
|
|
break;
|
|
}
|
|
}
|
|
/*Write the FM_CTL1 sequence*/
|
|
for (i = 0; i < 1; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL1_REG, fm_ctl1[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL1 failure: %d\n",
|
|
retval);
|
|
break;
|
|
}
|
|
}
|
|
/*Write second sequence of bytes to FM_CTL0*/
|
|
for (i = 0; i < 3; i++) {
|
|
retval = tavarua_write_register(radio,
|
|
BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "FM_CTL0:Set-2 failure: %d\n",
|
|
retval);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
retval = tavarua_write_registers(radio, FM_CTL0,
|
|
buffer, sizeof(buffer)/sizeof(buffer[0]));
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s: failed to bring down the FM Core\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
}
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
bt_status = marimba_get_bt_status(radio->marimba);
|
|
/* Set the index based on the bt status*/
|
|
index = bt_status ? 1 : 0;
|
|
/* Check for Bahama's existance and Bahama V2 variant*/
|
|
if (bahama_present
|
|
&& (bahama_version == 0x09 || bahama_version == 0x0a)) {
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
/* actual value itself used as mask*/
|
|
retval = marimba_write_bit_mask(&config,
|
|
BAHAMA_LDO_DREG_CTL0, &internal_vreg_ctl[bt_status][0],
|
|
1, internal_vreg_ctl[index][0]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:0xF0 write failed\n", __func__);
|
|
goto exit;
|
|
}
|
|
/* actual value itself used as mask*/
|
|
retval = marimba_write_bit_mask(&config,
|
|
BAHAMA_LDO_AREG_CTL0, &internal_vreg_ctl[bt_status][1],
|
|
1, internal_vreg_ctl[index][1]);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:0xF4 write failed\n", __func__);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
/* disable fm core */
|
|
radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA;
|
|
}
|
|
|
|
value = 0x00;
|
|
retval = marimba_write_bit_mask(&config, MARIMBA_XO_BUFF_CNTRL,
|
|
&value, 1, FM_ENABLE);
|
|
if (retval < 0) {
|
|
printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n", __func__);
|
|
goto exit;
|
|
}
|
|
exit:
|
|
FMDBG("%s, Calling fm_shutdown\n", __func__);
|
|
queue_delayed_work(radio->wqueue, &radio->work,
|
|
msecs_to_jiffies(TAVARUA_DELAY/2));
|
|
/* teardown gpio and pmic */
|
|
marimba_set_fm_status(radio->marimba, false);
|
|
wait_for_completion(&radio->shutdown_done);
|
|
radio->handle_irq = 1;
|
|
radio->lp_mode = 1;
|
|
radio->spur_table_size = 0;
|
|
atomic_inc(&radio->users);
|
|
radio->marimba->mod_id = SLAVE_ID_BAHAMA;
|
|
flush_workqueue(radio->wqueue);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* tavarua_fops - file operations interface
|
|
*/
|
|
static const struct v4l2_file_operations tavarua_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = tavarua_fops_read,
|
|
.write = tavarua_fops_write,
|
|
.ioctl = video_ioctl2,
|
|
.open = tavarua_fops_open,
|
|
.release = tavarua_fops_release,
|
|
};
|
|
|
|
/*************************************************************************
|
|
* Video4Linux Interface
|
|
*************************************************************************/
|
|
|
|
/*
|
|
* tavarua_v4l2_queryctrl - query control
|
|
*/
|
|
static struct v4l2_queryctrl tavarua_v4l2_queryctrl[] = {
|
|
{
|
|
.id = V4L2_CID_AUDIO_VOLUME,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Volume",
|
|
.minimum = 0,
|
|
.maximum = 15,
|
|
.step = 1,
|
|
.default_value = 15,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUDIO_BALANCE,
|
|
.flags = V4L2_CTRL_FLAG_DISABLED,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUDIO_BASS,
|
|
.flags = V4L2_CTRL_FLAG_DISABLED,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUDIO_TREBLE,
|
|
.flags = V4L2_CTRL_FLAG_DISABLED,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUDIO_MUTE,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Mute",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 1,
|
|
},
|
|
{
|
|
.id = V4L2_CID_AUDIO_LOUDNESS,
|
|
.flags = V4L2_CTRL_FLAG_DISABLED,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SRCHMODE,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Search mode",
|
|
.minimum = 0,
|
|
.maximum = 7,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SCANDWELL,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Search dwell time",
|
|
.minimum = 0,
|
|
.maximum = 7,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SRCHON,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Search on/off",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.step = 1,
|
|
.default_value = 1,
|
|
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_STATE,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "radio 0ff/rx/tx/reset",
|
|
.minimum = 0,
|
|
.maximum = 3,
|
|
.step = 1,
|
|
.default_value = 1,
|
|
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_REGION,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "radio standard",
|
|
.minimum = 0,
|
|
.maximum = 2,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Signal Threshold",
|
|
.minimum = 0x80,
|
|
.maximum = 0x7F,
|
|
.step = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Search PTY",
|
|
.minimum = 0,
|
|
.maximum = 31,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PI,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Search PI",
|
|
.minimum = 0,
|
|
.maximum = 0xFF,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Preset num",
|
|
.minimum = 0,
|
|
.maximum = 12,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_EMPHASIS,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Emphasis",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_RDS_STD,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "RDS standard",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_SPACING,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Channel spacing",
|
|
.minimum = 0,
|
|
.maximum = 2,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_RDSON,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "RDS on/off",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "RDS group mask",
|
|
.minimum = 0,
|
|
.maximum = 0xFFFFFFFF,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "RDS processing",
|
|
.minimum = 0,
|
|
.maximum = 0xFF,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "RDS data groups to buffer",
|
|
.minimum = 1,
|
|
.maximum = 21,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_PSALL,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "pass all ps strings",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_LP_MODE,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Low power mode",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_ANTENNA,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "headset/internal",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
.default_value = 0,
|
|
},
|
|
/* Private controls for FM TX*/
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Set PS REPEATCOUNT",
|
|
.minimum = 0,
|
|
.maximum = 15,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Stop PS NAME",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
},
|
|
{
|
|
.id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT,
|
|
.type = V4L2_CTRL_TYPE_BOOLEAN,
|
|
.name = "Stop RT",
|
|
.minimum = 0,
|
|
.maximum = 1,
|
|
},
|
|
{ .id = V4L2_CID_PRIVATE_SET_NOTCH_FILTER,
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.name = "Notch filter",
|
|
.minimum = 0,
|
|
.maximum = 2,
|
|
},
|
|
|
|
};
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_querycap
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to query device capabilities.
|
|
|
|
NOTE:
|
|
All V4L2 devices support the VIDIOC_QUERYCAP ioctl. It is used to identify
|
|
kernel devices compatible with this specification and to obtain information
|
|
about driver and hardware capabilities. The ioctl takes a pointer to a struct
|
|
v4l2_capability which is filled by the driver. When the driver is not
|
|
compatible with this specification the ioctl returns an EINVAL error code.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param capability: pointer to struct v4l2_capability.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The device is not compatible with this specification.
|
|
*/
|
|
static int tavarua_vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *capability)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
|
|
strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
|
|
strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
|
|
sprintf(capability->bus_info, "I2C");
|
|
capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
|
|
|
|
capability->version = radio->chipID;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_queryctrl
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to query the device and driver for supported video
|
|
controls (enumerate control items).
|
|
|
|
NOTE:
|
|
To query the attributes of a control, the applications set the id field of
|
|
a struct v4l2_queryctrl and call the VIDIOC_QUERYCTRL ioctl with a pointer
|
|
to this structure. The driver fills the rest of the structure or returns an
|
|
EINVAL error code when the id is invalid.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param qc: pointer to struct v4l2_queryctrl.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The struct v4l2_queryctrl id is invalid.
|
|
*/
|
|
static int tavarua_vidioc_queryctrl(struct file *file, void *priv,
|
|
struct v4l2_queryctrl *qc)
|
|
{
|
|
unsigned char i;
|
|
int retval = -EINVAL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(tavarua_v4l2_queryctrl); i++) {
|
|
if (qc->id && qc->id == tavarua_v4l2_queryctrl[i].id) {
|
|
memcpy(qc, &(tavarua_v4l2_queryctrl[i]), sizeof(*qc));
|
|
retval = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (retval < 0)
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": query conv4ltrol failed with %d\n", retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int update_spur_table(struct tavarua_device *radio)
|
|
{
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
unsigned char size = 0, tbl_size = 0;
|
|
int index = 0, offset = 0, addr = 0x0, val = 0;
|
|
int retval = 0, temp = 0, cnt = 0, j = 0;
|
|
|
|
memset(xfr_buf, 0x0, XFR_REG_NUM);
|
|
|
|
/* Read the SPUR Table Size */
|
|
retval = xfr_rdwr_data(radio, XFR_READ, 1, SPUR_TABLE_ADDR, &tbl_size);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to read SPUR table size\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
/* Calculate the new SPUR Register address */
|
|
val = addr = (SPUR_TABLE_START_ADDR + (tbl_size * 3));
|
|
|
|
/* Save the SPUR Table length configured by user*/
|
|
temp = radio->spur_table_size;
|
|
|
|
/* COnfigure the new spur table length */
|
|
size = (radio->spur_table_size + tbl_size);
|
|
retval = xfr_rdwr_data(radio, XFR_WRITE, 1, SPUR_TABLE_ADDR, &size);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to configure SPUR table size\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
/* Program the spur table entries */
|
|
for (cnt = 0; cnt < (temp / 4); cnt++) {
|
|
offset = 0;
|
|
for (j = 0; j < 4; j++) {
|
|
xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
|
|
radio->spur_data.freq[index]), 1);
|
|
xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
|
|
radio->spur_data.freq[index]), 0);
|
|
xfr_buf[offset++] =
|
|
radio->spur_data.rmssi[index];
|
|
index++;
|
|
}
|
|
retval = xfr_rdwr_data(radio, XFR_WRITE, (SPUR_DATA_SIZE * 4),
|
|
addr, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to program SPUR frequencies\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
addr += (SPUR_DATA_SIZE * 4);
|
|
}
|
|
|
|
/* Program the additional SPUR Frequencies */
|
|
temp = radio->spur_table_size;
|
|
temp = (temp % 4);
|
|
if (temp > 0) {
|
|
offset = 0;
|
|
for (j = 0; j < temp; j++) {
|
|
xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
|
|
radio->spur_data.freq[index]), 1);
|
|
xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
|
|
radio->spur_data.freq[index]), 0);
|
|
xfr_buf[offset++] =
|
|
radio->spur_data.rmssi[index];
|
|
index++;
|
|
}
|
|
size = (temp * SPUR_DATA_SIZE);
|
|
retval = xfr_rdwr_data(radio, XFR_WRITE, size, addr, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to program SPUR frequencies\n",
|
|
__func__);
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
|
|
unsigned long offset, unsigned char *buf) {
|
|
|
|
unsigned char xfr_buf[XFR_REG_NUM + 1];
|
|
int retval = 0, temp = 0;
|
|
|
|
/* zero initialize the buffer */
|
|
memset(xfr_buf, 0x0, XFR_REG_NUM);
|
|
|
|
/* save the 'size' parameter */
|
|
temp = size;
|
|
|
|
/* Populate the XFR bytes */
|
|
xfr_buf[XFR_MODE_OFFSET] = LSH_DATA(size, 1);
|
|
xfr_buf[XFR_ADDR_MSB_OFFSET] = GET_FREQ(offset, 1);
|
|
xfr_buf[XFR_ADDR_LSB_OFFSET] = GET_FREQ(offset, 0);
|
|
if (op == XFR_READ) {
|
|
if (size > XFR_REG_NUM) {
|
|
FMDERR("%s: Cant read more than 16 bytes\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
xfr_buf[XFR_MODE_OFFSET] |= (XFR_PEEK_MODE);
|
|
size = 3;
|
|
} else if (op == XFR_WRITE) {
|
|
if (size > (XFR_REG_NUM - 2)) {
|
|
FMDERR("%s: Cant write more than 14 bytes\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
xfr_buf[XFR_MODE_OFFSET] |= (XFR_POKE_MODE);
|
|
memcpy(&xfr_buf[XFR_DATA_OFFSET], buf, size);
|
|
size += 3;
|
|
}
|
|
|
|
/* Perform the XFR READ/WRITE operation */
|
|
init_completion(&radio->sync_req_done);
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, size);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to perform XFR operation\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
/*Wait for the XFR interrupt */
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(WAIT_TIMEOUT))) {
|
|
FMDERR("Timeout: No XFR interrupt");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* For XFR READ operation save the XFR data provided by the SOC.
|
|
* Firmware reads the data from the address specified and places
|
|
* them in to the registers XFRDAT0-XFRDAT15 which the host can read.
|
|
*/
|
|
size = temp;
|
|
if (op == XFR_READ) {
|
|
retval = tavarua_read_registers(radio, XFRDAT0, size);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to read the XFR data\n", __func__);
|
|
return retval;
|
|
}
|
|
if (buf != NULL)
|
|
memcpy(buf, &radio->registers[XFRDAT0], size);
|
|
else {
|
|
FMDERR("%s: No buffer to copy XFR data\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int peek_MPX_DCC(struct tavarua_device *radio)
|
|
{
|
|
int retval = 0;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
int MPX_DCC[] = { 0 };
|
|
int DCC = 0;
|
|
int ct = 0;
|
|
unsigned char size = 0;
|
|
|
|
/*
|
|
Poking the MPX_DCC_BYPASS register to freeze the
|
|
value of MPX_DCC from changing while we access it
|
|
*/
|
|
|
|
/*Poking the MPX_DCC_BYPASS register : 0x88C0 */
|
|
size = 0x01;
|
|
xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
|
|
xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB;
|
|
xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB;
|
|
xfr_buf[3] = 0x01;
|
|
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4);
|
|
if (retval < 0) {
|
|
FMDBG("Failed to write\n");
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*15);
|
|
|
|
for (ct = 0; ct < 5; ct++)
|
|
xfr_buf[ct] = 0;
|
|
|
|
/* Peeking Regs 0x88C2-0x88C4 */
|
|
size = 0x03;
|
|
xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
|
|
xfr_buf[1] = MPX_DCC_PEEK_MSB_REG1;
|
|
xfr_buf[2] = MPX_DCC_PEEK_LSB_REG1;
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
|
|
if (retval < 0) {
|
|
FMDBG("Failed to write\n");
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
retval = tavarua_read_registers(radio, XFRDAT0, 3);
|
|
if (retval < 0) {
|
|
printk(KERN_INFO "INT_DET: Read failure\n");
|
|
return retval;
|
|
}
|
|
MPX_DCC[0] = (int)radio->registers[XFRDAT0];
|
|
MPX_DCC[1] = (int)radio->registers[XFRDAT1];
|
|
MPX_DCC[2] = (int)radio->registers[XFRDAT2];
|
|
|
|
/*
|
|
Form the final MPX_DCC parameter
|
|
MPX_DCC[0] will form the LSB part
|
|
MPX_DCC[1] will be the middle part and 4 bits of
|
|
MPX_DCC[2] will be the MSB par of the 20-bit signed MPX_DCC
|
|
*/
|
|
|
|
DCC = ((int)MPX_DCC[2] << 16) | ((int)MPX_DCC[1] << 8) |
|
|
((int)MPX_DCC[0]);
|
|
|
|
/*
|
|
if bit-19 is '1',set remaining bits to '1' & make it -tive
|
|
*/
|
|
if (DCC & 0x00080000) {
|
|
FMDBG(KERN_INFO "bit-19 is '1'\n");
|
|
DCC |= 0xFFF00000;
|
|
}
|
|
|
|
/*
|
|
Poking the MPX_DCC_BYPASS register to be back to normal
|
|
*/
|
|
|
|
/*Poking the MPX_DCC_BYPASS register : 0x88C0 */
|
|
size = 0x01;
|
|
xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
|
|
xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB;
|
|
xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB;
|
|
xfr_buf[3] = 0x00;
|
|
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4);
|
|
if (retval < 0) {
|
|
FMDBG("Failed to write\n");
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
|
|
return DCC;
|
|
}
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_g_ctrl
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to get the value of a control.
|
|
|
|
NOTE:
|
|
To get the current value of a control, applications initialize the id field
|
|
of a struct v4l2_control and call the VIDIOC_G_CTRL ioctl with a pointer to
|
|
this structure.
|
|
|
|
When the id is invalid drivers return an EINVAL error code. When the value is
|
|
out of bounds drivers can choose to take the closest valid value or return an
|
|
ERANGE error code, whatever seems more appropriate.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param ctrl: pointer to struct v4l2_control.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The struct v4l2_control id is invalid.
|
|
@return ERANGE: The struct v4l2_control value is out of bounds.
|
|
@return EBUSY: The control is temporarily not changeable, possibly because
|
|
another applications took over control of the device function this control
|
|
belongs to.
|
|
*/
|
|
static int tavarua_vidioc_g_ctrl(struct file *file, void *priv,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval = 0;
|
|
int cnt = 0;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
signed char cRmssiThreshold;
|
|
signed char ioc;
|
|
unsigned char size = 0;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_AUDIO_VOLUME:
|
|
break;
|
|
case V4L2_CID_AUDIO_MUTE:
|
|
ctrl->value = radio->registers[IOCTRL] & 0x03 ;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE:
|
|
ctrl->value = radio->registers[SRCHCTRL] & SRCH_MODE;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL:
|
|
ctrl->value = (radio->registers[SRCHCTRL] & SCAN_DWELL) >> 4;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCHON:
|
|
ctrl->value = (radio->registers[SRCHCTRL] & SRCH_ON) >> 7 ;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_STATE:
|
|
ctrl->value = (radio->registers[RDCTRL] & 0x03);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_IOVERC:
|
|
retval = tavarua_read_registers(radio, IOVERC, 1);
|
|
if (retval < 0)
|
|
return retval;
|
|
ioc = radio->registers[IOVERC];
|
|
ctrl->value = ioc;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_INTDET:
|
|
size = 0x1;
|
|
xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
|
|
xfr_buf[1] = INTDET_PEEK_MSB;
|
|
xfr_buf[2] = INTDET_PEEK_LSB;
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
|
|
if (retval < 0) {
|
|
FMDBG("Failed to write\n");
|
|
return retval;
|
|
}
|
|
FMDBG("INT_DET:Sync write success\n");
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
/* Read the XFRDAT0 register populated by FM SoC */
|
|
retval = tavarua_read_registers(radio, XFRDAT0, 3);
|
|
if (retval < 0) {
|
|
FMDBG("INT_DET: Read failure\n");
|
|
return retval;
|
|
}
|
|
ctrl->value = radio->registers[XFRDAT0];
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_MPX_DCC:
|
|
ctrl->value = peek_MPX_DCC(radio);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_REGION:
|
|
ctrl->value = radio->region_params.region;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH:
|
|
retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDBG("[G IOCTL=V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
|
|
FMDBG("sync_read_xfr error: [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
/* Since RMSSI Threshold is signed value */
|
|
cRmssiThreshold = (signed char)xfr_buf[0];
|
|
ctrl->value = cRmssiThreshold;
|
|
FMDBG("cRmssiThreshold: %d\n", cRmssiThreshold);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY:
|
|
ctrl->value = radio->srch_params.srch_pty;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI:
|
|
ctrl->value = radio->srch_params.srch_pi;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT:
|
|
ctrl->value = radio->srch_params.preset_num;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS:
|
|
ctrl->value = radio->region_params.emphasis;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDS_STD:
|
|
ctrl->value = radio->region_params.rds_std;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SPACING:
|
|
ctrl->value = radio->region_params.spacing;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSON:
|
|
ctrl->value = radio->registers[RDSCTRL] & RDS_ON;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
if (retval > -1)
|
|
ctrl->value = (xfr_buf[8] << 24) |
|
|
(xfr_buf[9] << 16) |
|
|
(xfr_buf[10] << 8) |
|
|
xfr_buf[11];
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC:
|
|
retval = tavarua_read_registers(radio, ADVCTRL, 1);
|
|
if (retval > -1)
|
|
ctrl->value = radio->registers[ADVCTRL];
|
|
msleep(TAVARUA_DELAY*5);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA:
|
|
retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
|
|
FMDERR("sync_read_xfr [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
ctrl->value = (unsigned char)xfr_buf[4];
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
if (retval > -1)
|
|
ctrl->value = xfr_buf[1];
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_PSALL:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
if (retval > -1)
|
|
ctrl->value = xfr_buf[12] & RDS_CONFIG_PSALL;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_LP_MODE:
|
|
ctrl->value = radio->lp_mode;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_ANTENNA:
|
|
ctrl->value = GET_REG_FIELD(radio->registers[IOCTRL],
|
|
IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK);
|
|
break;
|
|
case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD:
|
|
size = 0x04;
|
|
xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
|
|
xfr_buf[1] = ON_CHANNEL_TH_MSB;
|
|
xfr_buf[2] = ON_CHANNEL_TH_LSB;
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
|
|
if (retval < 0) {
|
|
pr_err("%s: Failed to write\n", __func__);
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
retval = tavarua_read_registers(radio, XFRDAT0, 4);
|
|
if (retval < 0) {
|
|
pr_err("%s: On Ch. DET: Read failure\n", __func__);
|
|
return retval;
|
|
}
|
|
for (cnt = 0; cnt < 4; cnt++)
|
|
FMDBG("On-Channel data set is : 0x%x\t",
|
|
(int)radio->registers[XFRDAT0+cnt]);
|
|
|
|
ctrl->value = LSH_DATA(radio->registers[XFRDAT0], 24) |
|
|
LSH_DATA(radio->registers[XFRDAT0+1], 16) |
|
|
LSH_DATA(radio->registers[XFRDAT0+2], 8) |
|
|
(radio->registers[XFRDAT0+3]);
|
|
FMDBG("The On Channel Threshold value is : 0x%x", ctrl->value);
|
|
break;
|
|
case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD:
|
|
size = 0x04;
|
|
xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
|
|
xfr_buf[1] = OFF_CHANNEL_TH_MSB;
|
|
xfr_buf[2] = OFF_CHANNEL_TH_LSB;
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
|
|
if (retval < 0) {
|
|
pr_err("%s: Failed to write\n", __func__);
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
retval = tavarua_read_registers(radio, XFRDAT0, 4);
|
|
if (retval < 0) {
|
|
pr_err("%s: Off Ch. DET: Read failure\n", __func__);
|
|
return retval;
|
|
}
|
|
for (cnt = 0; cnt < 4; cnt++)
|
|
FMDBG("Off-channel data set is : 0x%x\t",
|
|
(int)radio->registers[XFRDAT0+cnt]);
|
|
|
|
ctrl->value = LSH_DATA(radio->registers[XFRDAT0], 24) |
|
|
LSH_DATA(radio->registers[XFRDAT0+1], 16) |
|
|
LSH_DATA(radio->registers[XFRDAT0+2], 8) |
|
|
(radio->registers[XFRDAT0+3]);
|
|
FMDBG("The Off Channel Threshold value is : 0x%x", ctrl->value);
|
|
break;
|
|
/*
|
|
* These IOCTL's are place holders to keep the
|
|
* driver compatible with change in frame works for IRIS
|
|
*/
|
|
case V4L2_CID_PRIVATE_SINR_THRESHOLD:
|
|
case V4L2_CID_PRIVATE_SINR_SAMPLES:
|
|
case V4L2_CID_PRIVATE_IRIS_GET_SINR:
|
|
retval = 0;
|
|
break;
|
|
case V4L2_CID_PRIVATE_VALID_CHANNEL:
|
|
ctrl->value = radio->is_station_valid;
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
if (retval < 0)
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": get control failed with %d, id: %d\n", retval, ctrl->id);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int tavarua_vidioc_s_ext_ctrls(struct file *file, void *priv,
|
|
struct v4l2_ext_controls *ctrl)
|
|
{
|
|
int retval = 0;
|
|
int bytes_to_copy;
|
|
int bytes_copied = 0;
|
|
int bytes_left = 0;
|
|
int chunk_index = 0;
|
|
char tx_data[XFR_REG_NUM];
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
char *data = NULL;
|
|
int extra_name_byte = 0;
|
|
int name_bytes = 0;
|
|
|
|
switch ((ctrl->controls[0]).id) {
|
|
case V4L2_CID_RDS_TX_PS_NAME: {
|
|
FMDBG("In V4L2_CID_RDS_TX_PS_NAME\n");
|
|
/*Pass a sample PS string */
|
|
|
|
chunk_index = 0;
|
|
bytes_copied = 0;
|
|
bytes_left = min((int)(ctrl->controls[0]).size,
|
|
MAX_PS_LENGTH);
|
|
data = (ctrl->controls[0]).string;
|
|
|
|
/* send payload to FM hardware */
|
|
while (bytes_left) {
|
|
chunk_index++;
|
|
FMDBG("chunk is %d", chunk_index);
|
|
bytes_to_copy = min(bytes_left, XFR_REG_NUM);
|
|
/*Clear the tx_data */
|
|
memset(tx_data, 0, XFR_REG_NUM);
|
|
if (copy_from_user(tx_data,
|
|
data + bytes_copied, bytes_to_copy))
|
|
return -EFAULT;
|
|
retval = sync_write_xfr(radio,
|
|
RDS_PS_0 + chunk_index, tx_data);
|
|
if (retval < 0) {
|
|
FMDBG("sync_write_xfr: %d", retval);
|
|
return retval;
|
|
}
|
|
bytes_copied += bytes_to_copy;
|
|
bytes_left -= bytes_to_copy;
|
|
}
|
|
memset(tx_data, 0, XFR_REG_NUM);
|
|
/*Write the PS Header*/
|
|
FMDBG("Writing PS header\n");
|
|
extra_name_byte = (bytes_copied%8) ? 1 : 0;
|
|
name_bytes = (bytes_copied/8) + extra_name_byte;
|
|
/*8 bytes are grouped as 1 name */
|
|
tx_data[0] = (name_bytes) & MASK_TXREPCOUNT;
|
|
tx_data[1] = radio->pty & MASK_PTY; /* PTY */
|
|
tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8);
|
|
tx_data[3] = radio->pi & MASK_PI_LSB;
|
|
/* TX ctrl + repeatCount*/
|
|
tx_data[4] = TX_ON |
|
|
(radio->ps_repeatcount & MASK_TXREPCOUNT);
|
|
retval = sync_write_xfr(radio, RDS_PS_0, tx_data);
|
|
if (retval < 0) {
|
|
FMDBG("sync_write_xfr returned %d", retval);
|
|
return retval;
|
|
}
|
|
} break;
|
|
case V4L2_CID_RDS_TX_RADIO_TEXT: {
|
|
chunk_index = 0;
|
|
bytes_copied = 0;
|
|
FMDBG("In V4L2_CID_RDS_TX_RADIO_TEXT\n");
|
|
/*Pass a sample PS string */
|
|
FMDBG("Passed RT String : %s\n",
|
|
(ctrl->controls[0]).string);
|
|
bytes_left =
|
|
min((int)(ctrl->controls[0]).size, MAX_RT_LENGTH);
|
|
data = (ctrl->controls[0]).string;
|
|
/* send payload to FM hardware */
|
|
while (bytes_left) {
|
|
chunk_index++;
|
|
bytes_to_copy = min(bytes_left, XFR_REG_NUM);
|
|
memset(tx_data, 0, XFR_REG_NUM);
|
|
if (copy_from_user(tx_data,
|
|
data + bytes_copied, bytes_to_copy))
|
|
return -EFAULT;
|
|
retval = sync_write_xfr(radio,
|
|
RDS_RT_0 + chunk_index, tx_data);
|
|
if (retval < 0)
|
|
return retval;
|
|
bytes_copied += bytes_to_copy;
|
|
bytes_left -= bytes_to_copy;
|
|
}
|
|
/*Write the RT Header */
|
|
tx_data[0] = bytes_copied;
|
|
/* PTY */
|
|
tx_data[1] = TX_ON | ((radio->pty & MASK_PTY) >> 8);
|
|
/* PI high */
|
|
tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8);
|
|
/* PI low */
|
|
tx_data[3] = radio->pi & MASK_PI_LSB;
|
|
retval = sync_write_xfr(radio, RDS_RT_0 , tx_data);
|
|
if (retval < 0)
|
|
return retval;
|
|
FMDBG("done RT writing: %d\n", retval);
|
|
} break;
|
|
default:
|
|
{
|
|
FMDBG("Shouldn't reach here\n");
|
|
retval = -1;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_s_ctrl
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to set the value of a control.
|
|
|
|
NOTE:
|
|
To change the value of a control, applications initialize the id and value
|
|
fields of a struct v4l2_control and call the VIDIOC_S_CTRL ioctl.
|
|
|
|
When the id is invalid drivers return an EINVAL error code. When the value is
|
|
out of bounds drivers can choose to take the closest valid value or return an
|
|
ERANGE error code, whatever seems more appropriate.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param ctrl: pointer to struct v4l2_control.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The struct v4l2_control id is invalid.
|
|
@return ERANGE: The struct v4l2_control value is out of bounds.
|
|
@return EBUSY: The control is temporarily not changeable, possibly because
|
|
another applications took over control of the device function this control
|
|
belongs to.
|
|
*/
|
|
static int tavarua_vidioc_s_ctrl(struct file *file, void *priv,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval = 0, size = 0, cnt = 0;
|
|
unsigned char value;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
unsigned char tx_data[XFR_REG_NUM];
|
|
unsigned char dis_buf[XFR_REG_NUM];
|
|
unsigned int freq = 0, mpx_dcc = 0;
|
|
unsigned long curr = 0, prev = 0;
|
|
|
|
memset(xfr_buf, 0x0, XFR_REG_NUM);
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_AUDIO_VOLUME:
|
|
break;
|
|
case V4L2_CID_AUDIO_MUTE:
|
|
value = (radio->registers[IOCTRL] & ~IOC_HRD_MUTE) |
|
|
(ctrl->value & 0x03);
|
|
retval = tavarua_write_register(radio, IOCTRL, value);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE:
|
|
value = (radio->registers[SRCHCTRL] & ~SRCH_MODE) |
|
|
ctrl->value;
|
|
radio->registers[SRCHCTRL] = value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL:
|
|
value = (radio->registers[SRCHCTRL] & ~SCAN_DWELL) |
|
|
(ctrl->value << 4);
|
|
radio->registers[SRCHCTRL] = value;
|
|
break;
|
|
/* start/stop search */
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCHON:
|
|
FMDBG("starting search\n");
|
|
tavarua_search(radio, ctrl->value, SRCH_DIR_UP);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_STATE:
|
|
/* check if already on */
|
|
radio->handle_irq = 1;
|
|
if (((ctrl->value == FM_RECV) || (ctrl->value == FM_TRANS))
|
|
&& !(radio->registers[RDCTRL] &
|
|
ctrl->value)) {
|
|
FMDBG("clearing flags\n");
|
|
init_completion(&radio->sync_xfr_start);
|
|
init_completion(&radio->sync_req_done);
|
|
radio->xfr_in_progress = 0;
|
|
radio->xfr_bytes_left = 0;
|
|
FMDBG("turning on ..\n");
|
|
retval = tavarua_start(radio, ctrl->value);
|
|
if (retval >= 0) {
|
|
/* Enabling 'SoftMute' & 'SignalBlending' */
|
|
value = (radio->registers[IOCTRL] |
|
|
IOC_SFT_MUTE | IOC_SIG_BLND);
|
|
retval = tavarua_write_register(radio,
|
|
IOCTRL, value);
|
|
if (retval < 0)
|
|
FMDBG("SMute and SBlending"
|
|
"not enabled\n");
|
|
}
|
|
}
|
|
/* check if off */
|
|
else if ((ctrl->value == FM_OFF) && radio->registers[RDCTRL]) {
|
|
radio->spur_table_size = 0;
|
|
FMDBG("%s: turning off...\n", __func__);
|
|
tavarua_write_register(radio, RDCTRL, ctrl->value);
|
|
/* flush the event and work queues */
|
|
kfifo_reset(&radio->data_buf[TAVARUA_BUF_EVENTS]);
|
|
flush_workqueue(radio->wqueue);
|
|
/*
|
|
* queue the READY event from the host side
|
|
* in case of FM off
|
|
*/
|
|
tavarua_q_event(radio, TAVARUA_EVT_RADIO_DISABLED);
|
|
|
|
FMDBG("%s, Disable All Interrupts\n", __func__);
|
|
/* disable irq */
|
|
dis_buf[STATUS_REG1] = 0x00;
|
|
dis_buf[STATUS_REG2] = 0x00;
|
|
dis_buf[STATUS_REG3] = TRANSFER;
|
|
retval = sync_write_xfr(radio, INT_CTRL, dis_buf);
|
|
if (retval < 0) {
|
|
pr_err("%s: failed to disable"
|
|
"Interrupts\n", __func__);
|
|
return retval;
|
|
}
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH:
|
|
FMDBG("Setting audio path ...\n");
|
|
if (ctrl->value == FM_DIGITAL_PATH) {
|
|
FMDBG("Digital audio path enabled ...\n");
|
|
retval = tavarua_set_audio_path(
|
|
TAVARUA_AUDIO_OUT_DIGITAL_ON,
|
|
TAVARUA_AUDIO_OUT_ANALOG_OFF);
|
|
if (retval < 0) {
|
|
FMDERR("Error in tavarua_set_audio_path"
|
|
" %d\n", retval);
|
|
}
|
|
} else if (ctrl->value == FM_ANALOG_PATH) {
|
|
FMDBG("Analog audio path enabled ...\n");
|
|
retval = tavarua_set_audio_path(
|
|
TAVARUA_AUDIO_OUT_DIGITAL_OFF,
|
|
TAVARUA_AUDIO_OUT_ANALOG_ON);
|
|
if (retval < 0) {
|
|
FMDERR("Error in tavarua_set_audio_path"
|
|
" %d\n", retval);
|
|
}
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM:
|
|
radio->enable_optimized_srch_alg = ctrl->value;
|
|
FMDBG("V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM : %d",
|
|
radio->enable_optimized_srch_alg);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_REGION:
|
|
retval = tavarua_set_region(radio, ctrl->value);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH:
|
|
retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
|
|
FMDERR("sync_read_xfr [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
/* RMSSI Threshold is a signed 8 bit value */
|
|
xfr_buf[0] = (unsigned char)ctrl->value;
|
|
xfr_buf[1] = (unsigned char)ctrl->value;
|
|
retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
|
|
FMDERR("sync_write_xfr [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY:
|
|
radio->srch_params.srch_pty = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI:
|
|
radio->srch_params.srch_pi = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT:
|
|
radio->srch_params.preset_num = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS:
|
|
radio->region_params.emphasis = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDS_STD:
|
|
radio->region_params.rds_std = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_SPACING:
|
|
radio->region_params.spacing = ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSON:
|
|
retval = 0;
|
|
if (ctrl->value != (radio->registers[RDSCTRL] & RDS_ON)) {
|
|
value = radio->registers[RDSCTRL] | ctrl->value;
|
|
retval = tavarua_write_register(radio, RDSCTRL, value);
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
if (retval < 0)
|
|
break;
|
|
xfr_buf[8] = (ctrl->value & 0xFF000000) >> 24;
|
|
xfr_buf[9] = (ctrl->value & 0x00FF0000) >> 16;
|
|
xfr_buf[10] = (ctrl->value & 0x0000FF00) >> 8;
|
|
xfr_buf[11] = (ctrl->value & 0x000000FF);
|
|
retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC:
|
|
value = radio->registers[ADVCTRL] | ctrl->value;
|
|
retval = tavarua_write_register(radio, ADVCTRL, value);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_AF_JUMP:
|
|
retval = tavarua_read_registers(radio, ADVCTRL, 1);
|
|
SET_REG_FIELD(radio->registers[ADVCTRL], ctrl->value,
|
|
RDSAF_OFFSET, RDSAF_MASK);
|
|
msleep(TAVARUA_DELAY*5);
|
|
retval = tavarua_write_register(radio,
|
|
ADVCTRL, radio->registers[ADVCTRL]);
|
|
msleep(TAVARUA_DELAY*5);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA:
|
|
retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
|
|
FMDERR("sync_read_xfr [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
xfr_buf[4] = (unsigned char)ctrl->value;
|
|
retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
|
|
FMDERR("sync_write_xfr [retval=%d]\n", retval);
|
|
break;
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_HLSI:
|
|
retval = tavarua_read_registers(radio, RDCTRL, 1);
|
|
SET_REG_FIELD(radio->registers[RDCTRL], ctrl->value,
|
|
RDCTRL_HLSI_OFFSET, RDCTRL_HLSI_MASK);
|
|
retval = tavarua_write_register(radio, RDCTRL,
|
|
radio->registers[RDCTRL]);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
if (retval < 0)
|
|
break;
|
|
xfr_buf[1] = ctrl->value;
|
|
retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_PSALL:
|
|
retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
value = ctrl->value & RDS_CONFIG_PSALL;
|
|
if (retval < 0)
|
|
break;
|
|
xfr_buf[12] &= ~RDS_CONFIG_PSALL;
|
|
xfr_buf[12] |= value;
|
|
retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_LP_MODE:
|
|
retval = 0;
|
|
if (ctrl->value == radio->lp_mode)
|
|
break;
|
|
if (ctrl->value) {
|
|
FMDBG("going into low power mode\n");
|
|
retval = tavarua_disable_interrupts(radio);
|
|
} else {
|
|
FMDBG("going into normal power mode\n");
|
|
tavarua_setup_interrupts(radio,
|
|
(radio->registers[RDCTRL] & 0x03));
|
|
}
|
|
break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_ANTENNA:
|
|
SET_REG_FIELD(radio->registers[IOCTRL], ctrl->value,
|
|
IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK);
|
|
break;
|
|
case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD:
|
|
size = 0x04;
|
|
/* Poking the value of ON Channel Threshold value */
|
|
xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
|
|
xfr_buf[1] = ON_CHANNEL_TH_MSB;
|
|
xfr_buf[2] = ON_CHANNEL_TH_LSB;
|
|
/* Data to be poked into the register */
|
|
xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24;
|
|
xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16;
|
|
xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8;
|
|
xfr_buf[6] = (ctrl->value & 0x000000FF);
|
|
|
|
for (cnt = 3; cnt < 7; cnt++) {
|
|
FMDBG("On-channel data to be poked is : %d",
|
|
(int)xfr_buf[cnt]);
|
|
}
|
|
|
|
retval = tavarua_write_registers(radio, XFRCTRL,
|
|
xfr_buf, size+3);
|
|
if (retval < 0) {
|
|
pr_err("%s: Failed to write\n", __func__);
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
break;
|
|
case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD:
|
|
size = 0x04;
|
|
/* Poking the value of OFF Channel Threshold value */
|
|
xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
|
|
xfr_buf[1] = OFF_CHANNEL_TH_MSB;
|
|
xfr_buf[2] = OFF_CHANNEL_TH_LSB;
|
|
/* Data to be poked into the register */
|
|
xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24;
|
|
xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16;
|
|
xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8;
|
|
xfr_buf[6] = (ctrl->value & 0x000000FF);
|
|
|
|
for (cnt = 3; cnt < 7; cnt++) {
|
|
FMDBG("Off-channel data to be poked is : %d",
|
|
(int)xfr_buf[cnt]);
|
|
}
|
|
|
|
retval = tavarua_write_registers(radio, XFRCTRL,
|
|
xfr_buf, size+3);
|
|
if (retval < 0) {
|
|
pr_err("%s: Failed to write\n", __func__);
|
|
return retval;
|
|
}
|
|
/*Wait for the XFR interrupt */
|
|
msleep(TAVARUA_DELAY*10);
|
|
break;
|
|
/* TX Controls */
|
|
|
|
case V4L2_CID_RDS_TX_PTY: {
|
|
radio->pty = ctrl->value;
|
|
} break;
|
|
case V4L2_CID_RDS_TX_PI: {
|
|
radio->pi = ctrl->value;
|
|
} break;
|
|
case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME: {
|
|
FMDBG("In STOP_RDS_TX_PS_NAME\n");
|
|
/*Pass a sample PS string */
|
|
memset(tx_data, '0', XFR_REG_NUM);
|
|
FMDBG("Writing PS header\n");
|
|
retval = sync_write_xfr(radio, RDS_PS_0, tx_data);
|
|
FMDBG("retval of PS Header write: %d", retval);
|
|
|
|
} break;
|
|
|
|
case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT: {
|
|
memset(tx_data, '0', XFR_REG_NUM);
|
|
FMDBG("Writing RT header\n");
|
|
retval = sync_write_xfr(radio, RDS_RT_0, tx_data);
|
|
FMDBG("retval of Header write: %d", retval);
|
|
|
|
} break;
|
|
|
|
case V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT: {
|
|
radio->ps_repeatcount = ctrl->value;
|
|
} break;
|
|
case V4L2_CID_TUNE_POWER_LEVEL: {
|
|
unsigned char tx_power_lvl_config[FM_TX_PWR_LVL_MAX+1] = {
|
|
0x85, /* tx_da<5:3> = 0 lpf<2:0> = 5*/
|
|
0x95, /* tx_da<5:3> = 2 lpf<2:0> = 5*/
|
|
0x9D, /* tx_da<5:3> = 3 lpf<2:0> = 5*/
|
|
0xA5, /* tx_da<5:3> = 4 lpf<2:0> = 5*/
|
|
0xAD, /* tx_da<5:3> = 5 lpf<2:0> = 5*/
|
|
0xB5, /* tx_da<5:3> = 6 lpf<2:0> = 5*/
|
|
0xBD, /* tx_da<5:3> = 7 lpf<2:0> = 5*/
|
|
0xBF /* tx_da<5:3> = 7 lpf<2:0> = 7*/
|
|
};
|
|
if (ctrl->value > FM_TX_PWR_LVL_MAX)
|
|
ctrl->value = FM_TX_PWR_LVL_MAX;
|
|
if (ctrl->value < FM_TX_PWR_LVL_0)
|
|
ctrl->value = FM_TX_PWR_LVL_0;
|
|
retval = sync_read_xfr(radio, PHY_TXGAIN, xfr_buf);
|
|
FMDBG("return for PHY_TXGAIN is %d", retval);
|
|
if (retval < 0) {
|
|
FMDBG("read failed");
|
|
break;
|
|
}
|
|
xfr_buf[2] = tx_power_lvl_config[ctrl->value];
|
|
retval = sync_write_xfr(radio, PHY_TXGAIN, xfr_buf);
|
|
FMDBG("return for write PHY_TXGAIN is %d", retval);
|
|
if (retval < 0)
|
|
FMDBG("write failed");
|
|
} break;
|
|
case V4L2_CID_PRIVATE_SOFT_MUTE:
|
|
radio->registers[IOCTRL] &= ~(IOC_SFT_MUTE);
|
|
if (ctrl->value)
|
|
radio->registers[IOCTRL] |= IOC_SFT_MUTE;
|
|
retval = tavarua_write_register(radio, IOCTRL,
|
|
radio->registers[IOCTRL]);
|
|
if (retval < 0)
|
|
FMDERR("Failed to enable/disable SMute\n");
|
|
break;
|
|
/*These IOCTL's are place holders to keep the
|
|
driver compatible with change in frame works for IRIS */
|
|
case V4L2_CID_PRIVATE_RIVA_ACCS_ADDR:
|
|
case V4L2_CID_PRIVATE_RIVA_ACCS_LEN:
|
|
case V4L2_CID_PRIVATE_RIVA_PEEK:
|
|
case V4L2_CID_PRIVATE_RIVA_POKE:
|
|
case V4L2_CID_PRIVATE_SSBI_ACCS_ADDR:
|
|
case V4L2_CID_PRIVATE_SSBI_PEEK:
|
|
case V4L2_CID_PRIVATE_SSBI_POKE:
|
|
case V4L2_CID_PRIVATE_RDS_GRP_COUNTERS:
|
|
case V4L2_CID_PRIVATE_SET_NOTCH_FILTER:
|
|
case V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION:
|
|
case V4L2_CID_PRIVATE_SINR_THRESHOLD:
|
|
case V4L2_CID_PRIVATE_SINR_SAMPLES:
|
|
case V4L2_CID_PRIVATE_SPUR_SELECTION:
|
|
retval = 0;
|
|
break;
|
|
case V4L2_CID_PRIVATE_SPUR_FREQ:
|
|
radio->spur_data.freq[radio->spur_table_size] =
|
|
ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI:
|
|
radio->spur_data.rmssi[radio->spur_table_size++] =
|
|
ctrl->value;
|
|
break;
|
|
case V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE:
|
|
retval = update_spur_table(radio);
|
|
break;
|
|
case V4L2_CID_PRIVATE_TX_TONE:
|
|
retval = 0;
|
|
memset(xfr_buf, 0, sizeof(xfr_buf));
|
|
switch (ctrl->value) {
|
|
case ONE_KHZ_LR_EQUA_0DBFS:
|
|
xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
|
|
= TONE_LEFT_RIGHT_CH_ENABLED;
|
|
xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
|
|
xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
|
|
break;
|
|
case ONE_KHZ_LEFTONLY_EQUA_0DBFS:
|
|
xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
|
|
= TONE_LEFT_CH_ENABLED;
|
|
xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
|
|
break;
|
|
case ONE_KHZ_RIGHTONLY_EQUA_0DBFS:
|
|
xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
|
|
= TONE_RIGHT_CH_ENABLED;
|
|
xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
|
|
break;
|
|
case ONE_KHZ_LR_EQUA_l8DBFS:
|
|
xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
|
|
= (LSH_DATA(TONE_SCALE_IND_12,
|
|
TONE_SCALING_SHIFT)
|
|
| TONE_LEFT_RIGHT_CH_ENABLED);
|
|
xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
|
|
xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
|
|
break;
|
|
case FIFTEEN_KHZ_LR_EQUA_l8DBFS:
|
|
xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
|
|
= (LSH_DATA(TONE_SCALE_IND_12,
|
|
TONE_SCALING_SHIFT)
|
|
| TONE_LEFT_RIGHT_CH_ENABLED);
|
|
xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x0F;
|
|
xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x0F;
|
|
break;
|
|
default:
|
|
retval = -1;
|
|
FMDERR("tone generator value not valid\n");
|
|
break;
|
|
}
|
|
if (retval >= 0) {
|
|
xfr_buf[TONE_GEN_CTRL_BYTE] = 0x01;
|
|
retval = sync_write_xfr(radio, XFR_EXT, xfr_buf);
|
|
}
|
|
if (retval < 0)
|
|
FMDERR("Tone generator failed\n");
|
|
break;
|
|
case V4L2_CID_PRIVATE_VALID_CHANNEL:
|
|
/* Do not notify the host of tune event */
|
|
atomic_set(&radio->validate_channel, 1);
|
|
|
|
FMDBG("Going into low power mode\n");
|
|
retval = tavarua_disable_interrupts(radio);
|
|
|
|
/*
|
|
* Tune to 50KHz adjacent channel. If the requested station
|
|
* falls in JAPAN band and on the lower band-limit, then the
|
|
* adjacnet channel to be considered is 50KHz to the right side
|
|
* of the requested station as firmware does not allows to tune
|
|
* to frequency outside the range: 76000KHz to 108000KHz.
|
|
*/
|
|
if (ctrl->value == REGION_JAPAN_STANDARD_BAND_LOW)
|
|
freq = (ctrl->value + ADJ_CHANNEL_KHZ);
|
|
else
|
|
freq = (ctrl->value - ADJ_CHANNEL_KHZ);
|
|
INIT_COMPLETION(radio->sync_req_done);
|
|
retval = tavarua_set_freq(radio, (freq * TUNE_MULT));
|
|
if (retval < 0) {
|
|
FMDERR("Failed to tune to adjacent station\n");
|
|
goto error;
|
|
}
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout))) {
|
|
FMDERR("Timeout: No Tune response\n");
|
|
retval = -ETIMEDOUT;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Wait for a minimum of 100ms for the firmware
|
|
* to start collecting the MPX_DCC values
|
|
*/
|
|
msleep(TAVARUA_DELAY * 10);
|
|
|
|
/* Compute MPX_DCC of adjacent station */
|
|
retval = compute_MPX_DCC(radio, &mpx_dcc);
|
|
if (retval < 0) {
|
|
FMDERR("Failed to get MPX_DCC of adjacent station\n");
|
|
goto error;
|
|
}
|
|
/* Calculate the absolute value of MPX_DCC */
|
|
prev = abs(mpx_dcc);
|
|
|
|
/* Tune back to original station */
|
|
INIT_COMPLETION(radio->sync_req_done);
|
|
retval = tavarua_set_freq(radio, (ctrl->value * TUNE_MULT));
|
|
if (retval < 0) {
|
|
FMDERR("Failed to tune to requested station\n");
|
|
goto error;
|
|
}
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout))) {
|
|
FMDERR("Timeout: No Tune response\n");
|
|
retval = -ETIMEDOUT;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Wait for a minimum of 100ms for the firmware
|
|
* to start collecting the MPX_DCC values
|
|
*/
|
|
msleep(TAVARUA_DELAY * 10);
|
|
|
|
/* Compute MPX_DCC of current station */
|
|
retval = compute_MPX_DCC(radio, &mpx_dcc);
|
|
if (retval < 0) {
|
|
FMDERR("Failed to get MPX_DCC of current station\n");
|
|
goto error;
|
|
}
|
|
/* Calculate the absolute value of MPX_DCC */
|
|
curr = abs(mpx_dcc);
|
|
|
|
FMDBG("Going into normal power mode\n");
|
|
tavarua_setup_interrupts(radio,
|
|
(radio->registers[RDCTRL] & 0x03));
|
|
|
|
FMDBG("Absolute MPX_DCC of current station : %lu\n", curr);
|
|
FMDBG("Absolute MPX_DCC of adjacent station : %lu\n", prev);
|
|
|
|
/*
|
|
* For valid stations, the absolute MPX_DCC value will be within
|
|
* the range 0 <= MPX_DCC <= 12566 and the MPX_DCC value of the
|
|
* adjacent station will be greater than 20,000.
|
|
*/
|
|
if ((curr <= MPX_DCC_LIMIT) &&
|
|
(prev > MPX_DCC_UPPER_LIMIT)) {
|
|
FMDBG("%d KHz is A VALID STATION!\n", ctrl->value);
|
|
radio->is_station_valid = VALID_CHANNEL;
|
|
} else {
|
|
FMDBG("%d KHz is NOT A VALID STATION!\n", ctrl->value);
|
|
radio->is_station_valid = INVALID_CHANNEL;
|
|
}
|
|
error:
|
|
atomic_set(&radio->validate_channel, 0);
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
if (retval < 0)
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": set control failed with %d, id : %d\n", retval, ctrl->id);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int compute_MPX_DCC(struct tavarua_device *radio, int *val)
|
|
{
|
|
|
|
int DCC = 0, retval = 0;
|
|
int MPX_DCC[3];
|
|
unsigned char value;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
|
|
/* Freeze the MPX_DCC value from changing */
|
|
value = CTRL_ON;
|
|
retval = xfr_rdwr_data(radio, XFR_WRITE, 1, MPX_DCC_BYPASS_REG, &value);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to freeze MPX_DCC\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
/* Measure the MPX_DCC of current station. */
|
|
retval = xfr_rdwr_data(radio, XFR_READ, 3, MPX_DCC_DATA_REG, xfr_buf);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to read MPX_DCC\n", __func__);
|
|
return retval;
|
|
}
|
|
MPX_DCC[0] = xfr_buf[0];
|
|
MPX_DCC[1] = xfr_buf[1];
|
|
MPX_DCC[2] = xfr_buf[2];
|
|
/*
|
|
* Form the final MPX_DCC parameter
|
|
* MPX_DCC[0] will form the LSB part
|
|
* MPX_DCC[1] will be the middle part and 4 bits of
|
|
* MPX_DCC[2] will be the MSB part of the 20-bit signed MPX_DCC
|
|
*/
|
|
DCC = (LSH_DATA(MPX_DCC[2], 16) | LSH_DATA(MPX_DCC[1], 8) | MPX_DCC[0]);
|
|
|
|
/* if bit-19 is '1',set remaining bits to '1' & make it -tive */
|
|
if (DCC & 0x00080000)
|
|
DCC |= 0xFFF00000;
|
|
|
|
*val = DCC;
|
|
|
|
/* Un-freeze the MPX_DCC value */
|
|
value = CTRL_OFF;
|
|
retval = xfr_rdwr_data(radio, XFR_WRITE, 1, 0x88C0, &value);
|
|
if (retval < 0) {
|
|
FMDERR("%s: Failed to un-freeze MPX_DCC\n", __func__);
|
|
return retval;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_g_tuner
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to get tuner attributes.
|
|
|
|
NOTE:
|
|
To query the attributes of a tuner, applications initialize the index field
|
|
and zero out the reserved array of a struct v4l2_tuner and call the
|
|
VIDIOC_G_TUNER ioctl with a pointer to this structure. Drivers fill the rest
|
|
of the structure or return an EINVAL error code when the index is out of
|
|
bounds. To enumerate all tuners applications shall begin at index zero,
|
|
incrementing by one until the driver returns EINVAL.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param tuner: pointer to struct v4l2_tuner.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The struct v4l2_tuner index is out of bounds.
|
|
*/
|
|
static int tavarua_vidioc_g_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *tuner)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval;
|
|
unsigned char xfr_buf[XFR_REG_NUM];
|
|
char rmssi = 0;
|
|
unsigned char size = 0;
|
|
|
|
if (tuner->index > 0)
|
|
return -EINVAL;
|
|
|
|
/* read status rssi */
|
|
retval = tavarua_read_registers(radio, IOCTRL, 1);
|
|
if (retval < 0)
|
|
return retval;
|
|
/* read RMSSI */
|
|
size = 0x1;
|
|
xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
|
|
xfr_buf[1] = RMSSI_PEEK_MSB;
|
|
xfr_buf[2] = RMSSI_PEEK_LSB;
|
|
retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
|
|
msleep(TAVARUA_DELAY*10);
|
|
retval = tavarua_read_registers(radio, XFRDAT0, 3);
|
|
rmssi = radio->registers[XFRDAT0];
|
|
tuner->signal = rmssi;
|
|
|
|
strcpy(tuner->name, "FM");
|
|
tuner->type = V4L2_TUNER_RADIO;
|
|
tuner->rangelow = radio->region_params.band_low;
|
|
tuner->rangehigh = radio->region_params.band_high;
|
|
tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
|
|
tuner->capability = V4L2_TUNER_CAP_LOW;
|
|
|
|
/* Stereo indicator == Stereo (instead of Mono) */
|
|
if (radio->registers[IOCTRL] & IOC_MON_STR)
|
|
tuner->audmode = V4L2_TUNER_MODE_STEREO;
|
|
else
|
|
tuner->audmode = V4L2_TUNER_MODE_MONO;
|
|
|
|
/* automatic frequency control: -1: freq to low, 1 freq to high */
|
|
tuner->afc = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_s_tuner
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to set tuner attributes. Used to set mono/stereo mode.
|
|
|
|
NOTE:
|
|
Tuners have two writable properties, the audio mode and the radio frequency.
|
|
To change the audio mode, applications initialize the index, audmode and
|
|
reserved fields and call the VIDIOC_S_TUNER ioctl. This will not change the
|
|
current tuner, which is determined by the current video input. Drivers may
|
|
choose a different audio mode if the requested mode is invalid or unsupported.
|
|
Since this is a write-only ioctl, it does not return the actually selected
|
|
audio mode.
|
|
|
|
To change the radio frequency the VIDIOC_S_FREQUENCY ioctl is available.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param tuner: pointer to struct v4l2_tuner.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return -EINVAL: The struct v4l2_tuner index is out of bounds.
|
|
*/
|
|
static int tavarua_vidioc_s_tuner(struct file *file, void *priv,
|
|
struct v4l2_tuner *tuner)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval;
|
|
int audmode;
|
|
if (tuner->index > 0)
|
|
return -EINVAL;
|
|
|
|
FMDBG("%s: set low to %d\n", __func__, tuner->rangelow);
|
|
radio->region_params.band_low = tuner->rangelow;
|
|
radio->region_params.band_high = tuner->rangehigh;
|
|
if (tuner->audmode == V4L2_TUNER_MODE_MONO)
|
|
/* Mono */
|
|
audmode = (radio->registers[IOCTRL] | IOC_MON_STR);
|
|
else
|
|
/* Stereo */
|
|
audmode = (radio->registers[IOCTRL] & ~IOC_MON_STR);
|
|
retval = tavarua_write_register(radio, IOCTRL, audmode);
|
|
if (retval < 0)
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": set tuner failed with %d\n", retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_g_frequency
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to get tuner or modulator radio frequency.
|
|
|
|
NOTE:
|
|
To get the current tuner or modulator radio frequency applications set the
|
|
tuner field of a struct v4l2_frequency to the respective tuner or modulator
|
|
number (only input devices have tuners, only output devices have modulators),
|
|
zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a
|
|
pointer to this structure. The driver stores the current frequency in the
|
|
frequency field.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param freq: pointer to struct v4l2_frequency. This will be set to the
|
|
resultant
|
|
frequency in 62.5 khz on success.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The tuner index is out of bounds or the value in the type
|
|
field is wrong.
|
|
*/
|
|
static int tavarua_vidioc_g_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *freq)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
freq->type = V4L2_TUNER_RADIO;
|
|
return tavarua_get_freq(radio, freq);
|
|
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_s_frequency
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to set tuner or modulator radio frequency.
|
|
|
|
NOTE:
|
|
To change the current tuner or modulator radio frequency applications
|
|
initialize the tuner, type and frequency fields, and the reserved array of
|
|
a struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer
|
|
to this structure. When the requested frequency is not possible the driver
|
|
assumes the closest possible value. However VIDIOC_S_FREQUENCY is a
|
|
write-only ioctl, it does not return the actual new frequency.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param freq: pointer to struct v4l2_frequency.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The tuner index is out of bounds or the value in the type
|
|
field is wrong.
|
|
*/
|
|
static int tavarua_vidioc_s_frequency(struct file *file, void *priv,
|
|
struct v4l2_frequency *freq)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int retval = -1;
|
|
struct v4l2_frequency getFreq;
|
|
|
|
FMDBG("%s\n", __func__);
|
|
|
|
if (freq->type != V4L2_TUNER_RADIO)
|
|
return -EINVAL;
|
|
|
|
FMDBG("Calling tavarua_set_freq\n");
|
|
|
|
INIT_COMPLETION(radio->sync_req_done);
|
|
retval = tavarua_set_freq(radio, freq->frequency);
|
|
if (retval < 0) {
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": set frequency failed with %d\n", retval);
|
|
} else {
|
|
/* Wait for interrupt i.e. complete
|
|
(&radio->sync_req_done); call */
|
|
if (!wait_for_completion_timeout(&radio->sync_req_done,
|
|
msecs_to_jiffies(wait_timeout))) {
|
|
FMDERR("Timeout: No Tune response");
|
|
retval = tavarua_get_freq(radio, &getFreq);
|
|
radio->tune_req = 0;
|
|
if (retval > 0) {
|
|
if (getFreq.frequency == freq->frequency) {
|
|
/** This is success, queut the event*/
|
|
tavarua_q_event(radio,
|
|
TAVARUA_EVT_TUNE_SUCC);
|
|
return 0;
|
|
} else {
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
radio->tune_req = 0;
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_dqbuf
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to exchange a buffer with the driver.
|
|
This is main buffer function, in essense its equivalent to a blocking
|
|
read call.
|
|
|
|
Applications call the VIDIOC_DQBUF ioctl to dequeue a filled (capturing) or
|
|
displayed (output) buffer from the driver's outgoing queue. They just set
|
|
the type and memory fields of a struct v4l2_buffer as above, when VIDIOC_DQBUF
|
|
is called with a pointer to this structure the driver fills the remaining
|
|
fields or returns an error code.
|
|
|
|
NOTE:
|
|
By default VIDIOC_DQBUF blocks when no buffer is in the outgoing queue.
|
|
When the O_NONBLOCK flag was given to the open() function, VIDIOC_DQBUF
|
|
returns immediately with an EAGAIN error code when no buffer is available.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param buffer: pointer to struct v4l2_buffer.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EAGAIN: Non-blocking I/O has been selected using O_NONBLOCK and no
|
|
buffer was in the outgoing queue.
|
|
@return EINVAL: The buffer type is not supported, or the index is out of
|
|
bounds, or no buffers have been allocated yet, or the userptr or length are
|
|
invalid.
|
|
@return ENOMEM: Not enough physical or virtual memory was available to enqueue
|
|
a user pointer buffer.
|
|
@return EIO: VIDIOC_DQBUF failed due to an internal error. Can also indicate
|
|
temporary problems like signal loss. Note the driver might dequeue an (empty)
|
|
buffer despite returning an error, or even stop capturing.
|
|
*/
|
|
static int tavarua_vidioc_dqbuf(struct file *file, void *priv,
|
|
struct v4l2_buffer *buffer)
|
|
{
|
|
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
enum tavarua_buf_t buf_type = -1;
|
|
unsigned char buf_fifo[STD_BUF_SIZE] = {0};
|
|
struct kfifo *data_fifo = NULL;
|
|
unsigned char *buf = NULL;
|
|
unsigned int len = 0, retval = -1;
|
|
|
|
if ((radio == NULL) || (buffer == NULL)) {
|
|
FMDERR("radio/buffer is NULL\n");
|
|
return -ENXIO;
|
|
}
|
|
buf_type = buffer->index;
|
|
buf = (unsigned char *)buffer->m.userptr;
|
|
len = buffer->length;
|
|
FMDBG("%s: requesting buffer %d\n", __func__, buf_type);
|
|
|
|
if ((buf_type < TAVARUA_BUF_MAX) && (buf_type >= 0)) {
|
|
data_fifo = &radio->data_buf[buf_type];
|
|
if (buf_type == TAVARUA_BUF_EVENTS) {
|
|
if (wait_event_interruptible(radio->event_queue,
|
|
kfifo_len(data_fifo)) < 0) {
|
|
return -EINTR;
|
|
}
|
|
}
|
|
} else {
|
|
FMDERR("invalid buffer type\n");
|
|
return -EINVAL;
|
|
}
|
|
if (len <= STD_BUF_SIZE) {
|
|
buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0],
|
|
len, &radio->buf_lock[buf_type]);
|
|
} else {
|
|
FMDERR("kfifo_out_locked can not use len more than 128\n");
|
|
return -EINVAL;
|
|
}
|
|
retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused);
|
|
if (retval > 0) {
|
|
FMDERR("Failed to copy %d bytes of data\n", retval);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_g_fmt_type_private
|
|
=============================================================================*/
|
|
/**
|
|
This function is here to make the v4l2 framework happy.
|
|
We cannot use private buffers without it.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param f: pointer to struct v4l2_format.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The tuner index is out of bounds or the value in the type
|
|
field is wrong.
|
|
*/
|
|
static int tavarua_vidioc_g_fmt_type_private(struct file *file, void *priv,
|
|
struct v4l2_format *f)
|
|
{
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*=============================================================================
|
|
FUNCTION: tavarua_vidioc_s_hw_freq_seek
|
|
=============================================================================*/
|
|
/**
|
|
This function is called to perform a hardware frequency seek.
|
|
|
|
Start a hardware frequency seek from the current frequency. To do this
|
|
applications initialize the tuner, type, seek_upward and wrap_around fields,
|
|
and zero out the reserved array of a struct v4l2_hw_freq_seek and call the
|
|
VIDIOC_S_HW_FREQ_SEEK ioctl with a pointer to this structure.
|
|
|
|
This ioctl is supported if the V4L2_CAP_HW_FREQ_SEEK capability is set.
|
|
|
|
@param file: File descriptor returned by open().
|
|
@param seek: pointer to struct v4l2_hw_freq_seek.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
@return EINVAL: The tuner index is out of bounds or the value in the type
|
|
field is wrong.
|
|
@return EAGAIN: The ioctl timed-out. Try again.
|
|
*/
|
|
static int tavarua_vidioc_s_hw_freq_seek(struct file *file, void *priv,
|
|
struct v4l2_hw_freq_seek *seek)
|
|
{
|
|
struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
|
|
int dir;
|
|
if (seek->seek_upward)
|
|
dir = SRCH_DIR_UP;
|
|
else
|
|
dir = SRCH_DIR_DOWN;
|
|
FMDBG("starting search\n");
|
|
return tavarua_search(radio, CTRL_ON, dir);
|
|
}
|
|
|
|
/*
|
|
* tavarua_viddev_tamples - video device interface
|
|
*/
|
|
static const struct v4l2_ioctl_ops tavarua_ioctl_ops = {
|
|
.vidioc_querycap = tavarua_vidioc_querycap,
|
|
.vidioc_queryctrl = tavarua_vidioc_queryctrl,
|
|
.vidioc_g_ctrl = tavarua_vidioc_g_ctrl,
|
|
.vidioc_s_ctrl = tavarua_vidioc_s_ctrl,
|
|
.vidioc_g_tuner = tavarua_vidioc_g_tuner,
|
|
.vidioc_s_tuner = tavarua_vidioc_s_tuner,
|
|
.vidioc_g_frequency = tavarua_vidioc_g_frequency,
|
|
.vidioc_s_frequency = tavarua_vidioc_s_frequency,
|
|
.vidioc_s_hw_freq_seek = tavarua_vidioc_s_hw_freq_seek,
|
|
.vidioc_dqbuf = tavarua_vidioc_dqbuf,
|
|
.vidioc_g_fmt_type_private = tavarua_vidioc_g_fmt_type_private,
|
|
.vidioc_s_ext_ctrls = tavarua_vidioc_s_ext_ctrls,
|
|
};
|
|
|
|
static struct video_device tavarua_viddev_template = {
|
|
.fops = &tavarua_fops,
|
|
.ioctl_ops = &tavarua_ioctl_ops,
|
|
.name = DRIVER_NAME,
|
|
.release = video_device_release,
|
|
};
|
|
|
|
/*==============================================================
|
|
FUNCTION: FmQSocCom_EnableInterrupts
|
|
==============================================================*/
|
|
/**
|
|
This function enable interrupts.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param state: FM radio state (receiver/transmitter/off/reset).
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_setup_interrupts(struct tavarua_device *radio,
|
|
enum radio_state_t state)
|
|
{
|
|
int retval;
|
|
unsigned char int_ctrl[XFR_REG_NUM];
|
|
|
|
if (!radio->lp_mode)
|
|
return 0;
|
|
|
|
int_ctrl[STATUS_REG1] = READY | TUNE | SEARCH | SCANNEXT |
|
|
SIGNAL | INTF | SYNC | AUDIO;
|
|
if (state == FM_RECV)
|
|
int_ctrl[STATUS_REG2] = RDSDAT | RDSRT | RDSPS | RDSAF;
|
|
else
|
|
int_ctrl[STATUS_REG2] = TXRDSDAT | TXRDSDONE;
|
|
|
|
int_ctrl[STATUS_REG3] = TRANSFER | ERROR;
|
|
|
|
/* use xfr for interrupt setup */
|
|
if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0
|
|
|| radio->chipID == BAHAMA_2_0 || radio->chipID == BAHAMA_2_1) {
|
|
FMDBG("Setting interrupts\n");
|
|
retval = sync_write_xfr(radio, INT_CTRL, int_ctrl);
|
|
/* use register write to setup interrupts */
|
|
} else {
|
|
retval = tavarua_write_register(radio,
|
|
STATUS_REG1, int_ctrl[STATUS_REG1]);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
retval = tavarua_write_register(radio,
|
|
STATUS_REG2, int_ctrl[STATUS_REG2]);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
retval = tavarua_write_register(radio,
|
|
STATUS_REG3, int_ctrl[STATUS_REG3]);
|
|
if (retval < 0)
|
|
return retval;
|
|
}
|
|
|
|
radio->lp_mode = 0;
|
|
/* tavarua_handle_interrupts force reads all the interrupt status
|
|
* registers and it is not valid for MBA 2.1
|
|
*/
|
|
if ((radio->chipID != MARIMBA_2_1) && (radio->chipID != BAHAMA_1_0)
|
|
&& (radio->chipID != BAHAMA_2_0)
|
|
&& (radio->chipID != BAHAMA_2_1))
|
|
tavarua_handle_interrupts(radio);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_disable_interrupts
|
|
==============================================================*/
|
|
/**
|
|
This function disables interrupts.
|
|
|
|
@param radio: structure pointer passed by client.
|
|
|
|
@return => 0 if successful.
|
|
@return < 0 if failure.
|
|
*/
|
|
static int tavarua_disable_interrupts(struct tavarua_device *radio)
|
|
{
|
|
unsigned char lpm_buf[XFR_REG_NUM];
|
|
int retval;
|
|
if (radio->lp_mode)
|
|
return 0;
|
|
FMDBG("%s\n", __func__);
|
|
/* In Low power mode, disable all the interrupts that are not being
|
|
waited by the Application */
|
|
lpm_buf[STATUS_REG1] = TUNE | SEARCH | SCANNEXT;
|
|
lpm_buf[STATUS_REG2] = 0x00;
|
|
lpm_buf[STATUS_REG3] = TRANSFER;
|
|
/* use xfr for interrupt setup */
|
|
wait_timeout = 100;
|
|
if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0
|
|
|| radio->chipID == BAHAMA_2_0 || radio->chipID == BAHAMA_2_1)
|
|
retval = sync_write_xfr(radio, INT_CTRL, lpm_buf);
|
|
/* use register write to setup interrupts */
|
|
else
|
|
retval = tavarua_write_registers(radio, STATUS_REG1, lpm_buf,
|
|
ARRAY_SIZE(lpm_buf));
|
|
|
|
/*INT_CTL writes may fail with TIME_OUT as all the
|
|
interrupts have been disabled
|
|
*/
|
|
if (retval > -1 || retval == -ETIME) {
|
|
radio->lp_mode = 1;
|
|
/*Consider timeout as a valid case here*/
|
|
retval = 0;
|
|
}
|
|
wait_timeout = WAIT_TIMEOUT;
|
|
return retval;
|
|
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_start
|
|
==============================================================*/
|
|
/**
|
|
Starts/enables the device (FM radio).
|
|
|
|
@param radio: structure pointer passed by client.
|
|
@param state: FM radio state (receiver/transmitter/off/reset).
|
|
|
|
@return On success 0 is returned, else error code.
|
|
*/
|
|
static int tavarua_start(struct tavarua_device *radio,
|
|
enum radio_state_t state)
|
|
{
|
|
|
|
int retval;
|
|
FMDBG("%s <%d>\n", __func__, state);
|
|
/* set geographic region */
|
|
radio->region_params.region = TAVARUA_REGION_US;
|
|
|
|
/* set radio mode */
|
|
retval = tavarua_write_register(radio, RDCTRL, state);
|
|
if (retval < 0)
|
|
return retval;
|
|
/* wait for radio to init */
|
|
msleep(RADIO_INIT_TIME);
|
|
/* enable interrupts */
|
|
tavarua_setup_interrupts(radio, state);
|
|
/* default region is US */
|
|
radio->region_params.band_low = US_LOW_BAND * FREQ_MUL;
|
|
radio->region_params.band_high = US_HIGH_BAND * FREQ_MUL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_suspend
|
|
==============================================================*/
|
|
/**
|
|
Save state and stop all devices in system.
|
|
|
|
@param pdev: platform device to be suspended.
|
|
@param state: Power state to put each device in.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
*/
|
|
static int tavarua_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct tavarua_device *radio = platform_get_drvdata(pdev);
|
|
int retval;
|
|
int users = 0;
|
|
printk(KERN_INFO DRIVER_NAME "%s: radio suspend\n\n", __func__);
|
|
if (radio) {
|
|
users = atomic_read(&radio->users);
|
|
if (!users) {
|
|
retval = tavarua_disable_interrupts(radio);
|
|
if (retval < 0) {
|
|
printk(KERN_INFO DRIVER_NAME
|
|
"tavarua_suspend error %d\n", retval);
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_resume
|
|
==============================================================*/
|
|
/**
|
|
Restore state of each device in system.
|
|
|
|
@param pdev: platform device to be resumed.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
*/
|
|
static int tavarua_resume(struct platform_device *pdev)
|
|
{
|
|
|
|
struct tavarua_device *radio = platform_get_drvdata(pdev);
|
|
int retval;
|
|
int users = 0;
|
|
printk(KERN_INFO DRIVER_NAME "%s: radio resume\n\n", __func__);
|
|
if (radio) {
|
|
users = atomic_read(&radio->users);
|
|
|
|
if (!users) {
|
|
retval = tavarua_setup_interrupts(radio,
|
|
(radio->registers[RDCTRL] & 0x03));
|
|
if (retval < 0) {
|
|
FMDERR("Fails to write RDCTRL");
|
|
msleep(TAVARUA_DELAY);
|
|
retval = tavarua_setup_interrupts(radio,
|
|
(radio->registers[RDCTRL] & 0x03));
|
|
if (retval < 0) {
|
|
FMDERR("Error in tavarua_resume %d\n",
|
|
retval);
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_set_audio_path
|
|
==============================================================*/
|
|
/**
|
|
This function will configure the audio path to and from the
|
|
FM core.
|
|
|
|
This interface is expected to be called from the multimedia
|
|
driver's thread. This interface should only be called when
|
|
the FM hardware is enabled. If the FM hardware is not
|
|
currently enabled, this interface will return an error.
|
|
|
|
@param digital_on: Digital audio from the FM core should be enabled/disbled.
|
|
@param analog_on: Analog audio from the FM core should be enabled/disbled.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
*/
|
|
int tavarua_set_audio_path(int digital_on, int analog_on)
|
|
{
|
|
struct tavarua_device *radio = private_data;
|
|
int rx_on = radio->registers[RDCTRL] & FM_RECV;
|
|
int retval = 0;
|
|
if (!radio)
|
|
return -ENOMEM;
|
|
/* RX */
|
|
FMDBG("%s: digital: %d analog: %d\n", __func__, digital_on, analog_on);
|
|
if ((radio->pdata != NULL) && (radio->pdata->config_i2s_gpio != NULL)) {
|
|
if (digital_on) {
|
|
retval = radio->pdata->config_i2s_gpio(FM_I2S_ON);
|
|
if (retval) {
|
|
pr_err("%s: config_i2s_gpio failed\n",
|
|
__func__);
|
|
}
|
|
} else {
|
|
retval = radio->pdata->config_i2s_gpio(FM_I2S_OFF);
|
|
if (retval) {
|
|
pr_err("%s: config_i2s_gpio failed\n",
|
|
__func__);
|
|
}
|
|
}
|
|
}
|
|
|
|
SET_REG_FIELD(radio->registers[AUDIOCTRL],
|
|
((rx_on && analog_on) ? 1 : 0),
|
|
AUDIORX_ANALOG_OFFSET,
|
|
AUDIORX_ANALOG_MASK);
|
|
SET_REG_FIELD(radio->registers[AUDIOCTRL],
|
|
((rx_on && digital_on) ? 1 : 0),
|
|
AUDIORX_DIGITAL_OFFSET,
|
|
AUDIORX_DIGITAL_MASK);
|
|
SET_REG_FIELD(radio->registers[AUDIOCTRL],
|
|
(rx_on ? 0 : 1),
|
|
AUDIOTX_OFFSET,
|
|
AUDIOTX_MASK);
|
|
/*
|
|
|
|
I2S Master/Slave configuration:
|
|
Setting the FM SoC as I2S Master/Slave
|
|
'false' - FM SoC is I2S Slave
|
|
'true' - FM SoC is I2S Master
|
|
|
|
We get this infomation from the respective target's board file :
|
|
MSM7x30 - FM SoC is I2S Slave
|
|
MSM8x60 - FM SoC is I2S Slave
|
|
MSM7x27A - FM SoC is I2S Master
|
|
*/
|
|
|
|
if (!radio->pdata->is_fm_soc_i2s_master) {
|
|
FMDBG("FM SoC is I2S Slave\n");
|
|
SET_REG_FIELD(radio->registers[AUDIOCTRL],
|
|
(0),
|
|
I2SCTRL_OFFSET,
|
|
I2SCTRL_MASK);
|
|
} else {
|
|
FMDBG("FM SoC is I2S Master\n");
|
|
SET_REG_FIELD(radio->registers[AUDIOCTRL],
|
|
(1),
|
|
I2SCTRL_OFFSET,
|
|
I2SCTRL_MASK);
|
|
}
|
|
FMDBG("%s: %x\n", __func__, radio->registers[AUDIOCTRL]);
|
|
return tavarua_write_register(radio, AUDIOCTRL,
|
|
radio->registers[AUDIOCTRL]);
|
|
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_probe
|
|
==============================================================*/
|
|
/**
|
|
Once called this functions initiates, allocates resources and registers video
|
|
tuner device with the v4l2 framework.
|
|
|
|
NOTE:
|
|
probe() should verify that the specified device hardware
|
|
actually exists; sometimes platform setup code can't be sure. The probing
|
|
can use device resources, including clocks, and device platform_data.
|
|
|
|
@param pdev: platform device to be probed.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
-ENOMEM in low memory cases
|
|
*/
|
|
static int __init tavarua_probe(struct platform_device *pdev)
|
|
{
|
|
|
|
struct marimba_fm_platform_data *tavarua_pdata;
|
|
struct tavarua_device *radio;
|
|
int retval;
|
|
int i;
|
|
FMDBG("%s: probe called\n", __func__);
|
|
/* private data allocation */
|
|
radio = kzalloc(sizeof(struct tavarua_device), GFP_KERNEL);
|
|
if (!radio) {
|
|
retval = -ENOMEM;
|
|
goto err_initial;
|
|
}
|
|
|
|
radio->marimba = platform_get_drvdata(pdev);
|
|
tavarua_pdata = pdev->dev.platform_data;
|
|
radio->pdata = tavarua_pdata;
|
|
radio->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, radio);
|
|
|
|
/* video device allocation */
|
|
radio->videodev = video_device_alloc();
|
|
if (!radio->videodev)
|
|
goto err_radio;
|
|
|
|
/* initial configuration */
|
|
memcpy(radio->videodev, &tavarua_viddev_template,
|
|
sizeof(tavarua_viddev_template));
|
|
|
|
/*allocate internal buffers for decoded rds and event buffer*/
|
|
for (i = 0; i < TAVARUA_BUF_MAX; i++) {
|
|
int kfifo_alloc_rc=0;
|
|
spin_lock_init(&radio->buf_lock[i]);
|
|
|
|
if (i == TAVARUA_BUF_RAW_RDS)
|
|
kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
|
|
rds_buf*3, GFP_KERNEL);
|
|
else if (i == TAVARUA_BUF_RT_RDS)
|
|
kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
|
|
STD_BUF_SIZE * 2, GFP_KERNEL);
|
|
else
|
|
kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
|
|
STD_BUF_SIZE, GFP_KERNEL);
|
|
|
|
if (kfifo_alloc_rc!=0) {
|
|
printk(KERN_ERR "%s: failed allocating buffers %d\n",
|
|
__func__, kfifo_alloc_rc);
|
|
goto err_bufs;
|
|
}
|
|
}
|
|
/* initializing the device count */
|
|
atomic_set(&radio->users, 1);
|
|
radio->xfr_in_progress = 0;
|
|
radio->xfr_bytes_left = 0;
|
|
for (i = 0; i < TAVARUA_XFR_MAX; i++)
|
|
radio->pending_xfrs[i] = 0;
|
|
|
|
/* init transmit data */
|
|
radio->tx_mode = TAVARUA_TX_RT;
|
|
/* Init RT and PS Tx datas*/
|
|
radio->pty = 0;
|
|
radio->pi = 0;
|
|
radio->ps_repeatcount = 0;
|
|
/* init search params */
|
|
radio->srch_params.srch_pty = 0;
|
|
radio->srch_params.srch_pi = 0;
|
|
radio->srch_params.preset_num = 0;
|
|
radio->srch_params.get_list = 0;
|
|
/* radio initializes to low power mode */
|
|
radio->lp_mode = 1;
|
|
radio->handle_irq = 1;
|
|
/* init lock */
|
|
mutex_init(&radio->lock);
|
|
/* init completion flags */
|
|
init_completion(&radio->sync_xfr_start);
|
|
init_completion(&radio->sync_req_done);
|
|
radio->tune_req = 0;
|
|
/* initialize wait queue for event read */
|
|
init_waitqueue_head(&radio->event_queue);
|
|
/* initialize wait queue for raw rds read */
|
|
init_waitqueue_head(&radio->read_queue);
|
|
|
|
video_set_drvdata(radio->videodev, radio);
|
|
/*Start the worker thread for event handling and register read_int_stat
|
|
as worker function*/
|
|
radio->wqueue = create_singlethread_workqueue("kfmradio");
|
|
if (!radio->wqueue)
|
|
return -ENOMEM;
|
|
|
|
/* register video device */
|
|
if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) {
|
|
printk(KERN_WARNING DRIVER_NAME
|
|
": Could not register video device\n");
|
|
goto err_all;
|
|
}
|
|
private_data = radio;
|
|
return 0;
|
|
|
|
err_all:
|
|
video_device_release(radio->videodev);
|
|
err_bufs:
|
|
for (; i > -1; i--)
|
|
kfifo_free(&radio->data_buf[i]);
|
|
err_radio:
|
|
kfree(radio);
|
|
err_initial:
|
|
return retval;
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: tavarua_remove
|
|
==============================================================*/
|
|
/**
|
|
Removes the device.
|
|
|
|
@param pdev: platform device to be removed.
|
|
|
|
@return On success 0 is returned, else error code.
|
|
*/
|
|
static int __devexit tavarua_remove(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
struct tavarua_device *radio = platform_get_drvdata(pdev);
|
|
|
|
/* disable irq */
|
|
tavarua_disable_irq(radio);
|
|
|
|
destroy_workqueue(radio->wqueue);
|
|
|
|
video_unregister_device(radio->videodev);
|
|
|
|
/* free internal buffers */
|
|
for (i = 0; i < TAVARUA_BUF_MAX; i++)
|
|
kfifo_free(&radio->data_buf[i]);
|
|
|
|
/* free state struct */
|
|
kfree(radio);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Platform drivers follow the standard driver model convention, where
|
|
discovery/enumeration is handled outside the drivers, and drivers
|
|
provide probe() and remove() methods. They support power management
|
|
and shutdown notifications using the standard conventions.
|
|
*/
|
|
static struct platform_driver tavarua_driver = {
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "marimba_fm",
|
|
},
|
|
.remove = __devexit_p(tavarua_remove),
|
|
.suspend = tavarua_suspend,
|
|
.resume = tavarua_resume,
|
|
}; /* platform device we're adding */
|
|
|
|
|
|
/*************************************************************************
|
|
* Module Interface
|
|
************************************************************************/
|
|
|
|
/*==============================================================
|
|
FUNCTION: radio_module_init
|
|
==============================================================*/
|
|
/**
|
|
Module entry - add a platform-level device.
|
|
|
|
@return Returns zero if the driver registered and bound to a device, else
|
|
returns a negative error code when the driver not registered.
|
|
*/
|
|
static int __init radio_module_init(void)
|
|
{
|
|
printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
|
|
return platform_driver_probe(&tavarua_driver, tavarua_probe);
|
|
}
|
|
|
|
/*==============================================================
|
|
FUNCTION: radio_module_exit
|
|
==============================================================*/
|
|
/**
|
|
Module exit - removes a platform-level device.
|
|
|
|
NOTE:
|
|
Note that this function will also release all memory- and port-based
|
|
resources owned by the device (dev->resource).
|
|
|
|
@return none.
|
|
*/
|
|
static void __exit radio_module_exit(void)
|
|
{
|
|
platform_driver_unregister(&tavarua_driver);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
module_init(radio_module_init);
|
|
module_exit(radio_module_exit);
|
|
|