blob: 34bc76c644b93be227f22f937d3e210263f82aaf [file] [log] [blame]
/*
Copyright (c), 2004-2005,2007-2010 Trident Microsystems, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Trident Microsystems nor Hauppauge Computer Works
nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file $Id: drx_driver.c,v 1.40 2010/01/12 01:24:56 lfeng Exp $
*
* \brief Generic DRX functionality, DRX driver core.
*
*/
/*------------------------------------------------------------------------------
INCLUDE FILES
------------------------------------------------------------------------------*/
#include "drx_driver.h"
#define VERSION_FIXED 0
#if VERSION_FIXED
#define VERSION_MAJOR 0
#define VERSION_MINOR 0
#define VERSION_PATCH 0
#else
#include "drx_driver_version.h"
#endif
/*------------------------------------------------------------------------------
DEFINES
------------------------------------------------------------------------------*/
/*============================================================================*/
/*=== MICROCODE RELATED DEFINES ==============================================*/
/*============================================================================*/
/** \brief Magic word for checking correct Endianess of microcode data. */
#ifndef DRX_UCODE_MAGIC_WORD
#define DRX_UCODE_MAGIC_WORD ((((u16)'H')<<8)+((u16)'L'))
#endif
/** \brief CRC flag in ucode header, flags field. */
#ifndef DRX_UCODE_CRC_FLAG
#define DRX_UCODE_CRC_FLAG (0x0001)
#endif
/** \brief Compression flag in ucode header, flags field. */
#ifndef DRX_UCODE_COMPRESSION_FLAG
#define DRX_UCODE_COMPRESSION_FLAG (0x0002)
#endif
/** \brief Maximum size of buffer used to verify the microcode.
Must be an even number. */
#ifndef DRX_UCODE_MAX_BUF_SIZE
#define DRX_UCODE_MAX_BUF_SIZE (DRXDAP_MAX_RCHUNKSIZE)
#endif
#if DRX_UCODE_MAX_BUF_SIZE & 1
#error DRX_UCODE_MAX_BUF_SIZE must be an even number
#endif
/*============================================================================*/
/*=== CHANNEL SCAN RELATED DEFINES ===========================================*/
/*============================================================================*/
/**
* \brief Maximum progress indication.
*
* Progress indication will run from 0 upto DRX_SCAN_MAX_PROGRESS during scan.
*
*/
#ifndef DRX_SCAN_MAX_PROGRESS
#define DRX_SCAN_MAX_PROGRESS 1000
#endif
/*============================================================================*/
/*=== MACROS =================================================================*/
/*============================================================================*/
#define DRX_ISPOWERDOWNMODE(mode) ((mode == DRX_POWER_MODE_9) || \
(mode == DRX_POWER_MODE_10) || \
(mode == DRX_POWER_MODE_11) || \
(mode == DRX_POWER_MODE_12) || \
(mode == DRX_POWER_MODE_13) || \
(mode == DRX_POWER_MODE_14) || \
(mode == DRX_POWER_MODE_15) || \
(mode == DRX_POWER_MODE_16) || \
(mode == DRX_POWER_DOWN))
/*------------------------------------------------------------------------------
GLOBAL VARIABLES
------------------------------------------------------------------------------*/
/*------------------------------------------------------------------------------
STRUCTURES
------------------------------------------------------------------------------*/
/** \brief Structure of the microcode block headers */
typedef struct {
u32 addr;
/**< Destination address of the data in this block */
u16 size;
/**< Size of the block data following this header counted in
16 bits words */
u16 flags;
/**< Flags for this data block:
- bit[0]= CRC on/off
- bit[1]= compression on/off
- bit[15..2]=reserved */
u16 CRC;/**< CRC value of the data block, only valid if CRC flag is
set. */
} drxu_code_block_hdr_t, *pdrxu_code_block_hdr_t;
/*------------------------------------------------------------------------------
FUNCTIONS
------------------------------------------------------------------------------*/
/*============================================================================*/
/*============================================================================*/
/*== Channel Scan Functions ==================================================*/
/*============================================================================*/
/*============================================================================*/
#ifndef DRX_EXCLUDE_SCAN
/* Prototype of default scanning function */
static int
scan_function_default(void *scan_context,
enum drx_scan_command scan_command,
struct drx_channel *scan_channel, bool *get_next_channel);
/**
* \brief Get pointer to scanning function.
* \param demod: Pointer to demodulator instance.
* \return drx_scan_func_t.
*/
static drx_scan_func_t get_scan_function(struct drx_demod_instance *demod)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
drx_scan_func_t scan_func = (drx_scan_func_t) (NULL);
/* get scan function from common attributes */
common_attr = (struct drx_common_attr *) demod->my_common_attr;
scan_func = common_attr->scan_function;
if (scan_func != NULL) {
/* return device-specific scan function if it's not NULL */
return scan_func;
}
/* otherwise return default scan function in core driver */
return &scan_function_default;
}
/**
* \brief Get Context pointer.
* \param demod: Pointer to demodulator instance.
* \param scan_context: Context Pointer.
* \return drx_scan_func_t.
*/
static void *get_scan_context(struct drx_demod_instance *demod, void *scan_context)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
/* get scan function from common attributes */
common_attr = (struct drx_common_attr *) demod->my_common_attr;
scan_context = common_attr->scan_context;
if (scan_context == NULL)
scan_context = (void *)demod;
return scan_context;
}
/**
* \brief Wait for lock while scanning.
* \param demod: Pointer to demodulator instance.
* \param lock_stat: Pointer to bool indicating if end result is lock or not.
* \return int.
* \retval 0: Success
* \retval -EIO: I2C failure or bsp function failure.
*
* Wait until timeout, desired lock or NEVER_LOCK.
* Assume:
* - lock function returns : at least DRX_NOT_LOCKED and a lock state
* higher than DRX_NOT_LOCKED.
* - BSP has a clock function to retrieve a millisecond ticker value.
* - BSP has a sleep function to enable sleep of n millisecond.
*
* In case DRX_NEVER_LOCK is returned the poll-wait will be aborted.
*
*/
static int scan_wait_for_lock(struct drx_demod_instance *demod, bool *is_locked)
{
bool done_waiting = false;
enum drx_lock_status lock_state = DRX_NOT_LOCKED;
enum drx_lock_status desired_lock_state = DRX_NOT_LOCKED;
u32 timeout_value = 0;
u32 start_time_lock_stage = 0;
u32 current_time = 0;
u32 timer_value = 0;
*is_locked = false;
timeout_value = (u32) demod->my_common_attr->scan_demod_lock_timeout;
desired_lock_state = demod->my_common_attr->scan_desired_lock;
start_time_lock_stage = drxbsp_hst_clock();
/* Start polling loop, checking for lock & timeout */
while (!done_waiting) {
if (drx_ctrl(demod, DRX_CTRL_LOCK_STATUS, &lock_state) !=
0) {
return -EIO;
}
current_time = drxbsp_hst_clock();
timer_value = current_time - start_time_lock_stage;
if (lock_state >= desired_lock_state) {
*is_locked = true;
done_waiting = true;
} /* if ( lock_state >= desired_lock_state ) .. */
else if (lock_state == DRX_NEVER_LOCK) {
done_waiting = true;
} /* if ( lock_state == DRX_NEVER_LOCK ) .. */
else if (timer_value > timeout_value) {
/* lock_state == DRX_NOT_LOCKED and timeout */
done_waiting = true;
} else {
if (drxbsp_hst_sleep(10) != 0) {
return -EIO;
}
} /* if ( timer_value > timeout_value ) .. */
} /* while */
return 0;
}
/*============================================================================*/
/**
* \brief Determine next frequency to scan.
* \param demod: Pointer to demodulator instance.
* \param skip : Minimum frequency step to take.
* \return int.
* \retval 0: Succes.
* \retval -EINVAL: Invalid frequency plan.
*
* Helper function for ctrl_scan_next() function.
* Compute next frequency & index in frequency plan.
* Check if scan is ready.
*
*/
static int
scan_prepare_next_scan(struct drx_demod_instance *demod, s32 skip)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
u16 table_index = 0;
u16 frequency_plan_size = 0;
struct drx_frequency_plan *frequency_plan = (struct drx_frequency_plan *) (NULL);
s32 next_frequency = 0;
s32 tuner_min_frequency = 0;
s32 tuner_max_frequency = 0;
common_attr = (struct drx_common_attr *) demod->my_common_attr;
table_index = common_attr->scan_freq_plan_index;
frequency_plan = common_attr->scan_param->frequency_plan;
next_frequency = common_attr->scan_next_frequency;
tuner_min_frequency = common_attr->tuner_min_freq_rf;
tuner_max_frequency = common_attr->tuner_max_freq_rf;
do {
/* Search next frequency to scan */
/* always take at least one step */
(common_attr->scan_channels_scanned)++;
next_frequency += frequency_plan[table_index].step;
skip -= frequency_plan[table_index].step;
/* and then as many steps necessary to exceed 'skip'
without exceeding end of the band */
while ((skip > 0) &&
(next_frequency <= frequency_plan[table_index].last)) {
(common_attr->scan_channels_scanned)++;
next_frequency += frequency_plan[table_index].step;
skip -= frequency_plan[table_index].step;
}
/* reset skip, in case we move to the next band later */
skip = 0;
if (next_frequency > frequency_plan[table_index].last) {
/* reached end of this band */
table_index++;
frequency_plan_size =
common_attr->scan_param->frequency_plan_size;
if (table_index >= frequency_plan_size) {
/* reached end of frequency plan */
common_attr->scan_ready = true;
} else {
next_frequency = frequency_plan[table_index].first;
}
}
if (next_frequency > (tuner_max_frequency)) {
/* reached end of tuner range */
common_attr->scan_ready = true;
}
} while ((next_frequency < tuner_min_frequency) &&
(!common_attr->scan_ready));
/* Store new values */
common_attr->scan_freq_plan_index = table_index;
common_attr->scan_next_frequency = next_frequency;
return 0;
}
/*============================================================================*/
/**
* \brief Default DTV scanning function.
*
* \param demod: Pointer to demodulator instance.
* \param scan_command: Scanning command: INIT, NEXT or STOP.
* \param scan_channel: Channel to check: frequency and bandwidth, others AUTO
* \param get_next_channel: Return true if next frequency is desired at next call
*
* \return int.
* \retval 0: Channel found, DRX_CTRL_GET_CHANNEL can be used
* to retrieve channel parameters.
* \retval -EBUSY: Channel not found (yet).
* \retval -EIO: Something went wrong.
*
* scan_channel and get_next_channel will be NULL for INIT and STOP.
*/
static int
scan_function_default(void *scan_context,
enum drx_scan_command scan_command,
struct drx_channel *scan_channel, bool *get_next_channel)
{
struct drx_demod_instance *demod = NULL;
int status = -EIO;
bool is_locked = false;
demod = (struct drx_demod_instance *) scan_context;
if (scan_command != DRX_SCAN_COMMAND_NEXT) {
/* just return OK if not doing "scan next" */
return 0;
}
*get_next_channel = false;
status = drx_ctrl(demod, DRX_CTRL_SET_CHANNEL, scan_channel);
if (status != 0) {
return status;
}
status = scan_wait_for_lock(demod, &is_locked);
if (status != 0) {
return status;
}
/* done with this channel, move to next one */
*get_next_channel = true;
if (!is_locked) {
/* no channel found */
return -EBUSY;
}
/* channel found */
return 0;
}
/*============================================================================*/
/**
* \brief Initialize for channel scan.
* \param demod: Pointer to demodulator instance.
* \param scan_param: Pointer to scan parameters.
* \return int.
* \retval 0: Initialized for scan.
* \retval -EIO: No overlap between frequency plan and tuner
* range.
* \retval -EINVAL: Wrong parameters.
*
* This function should be called before starting a complete channel scan.
* It will prepare everything for a complete channel scan.
* After calling this function the DRX_CTRL_SCAN_NEXT control function can be
* used to perform the actual scanning. Scanning will start at the first
* center frequency of the frequency plan that is within the tuner range.
*
*/
static int
ctrl_scan_init(struct drx_demod_instance *demod, struct drx_scan_param *scan_param)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
s32 max_tuner_freq = 0;
s32 min_tuner_freq = 0;
u16 nr_channels_in_plan = 0;
u16 i = 0;
void *scan_context = NULL;
common_attr = (struct drx_common_attr *) demod->my_common_attr;
common_attr->scan_active = true;
/* invalidate a previous SCAN_INIT */
common_attr->scan_param = NULL;
common_attr->scan_next_frequency = 0;
/* Check parameters */
if (((demod->my_tuner == NULL) &&
(scan_param->num_tries != 1)) ||
(scan_param == NULL) ||
(scan_param->num_tries == 0) ||
(scan_param->frequency_plan == NULL) ||
(scan_param->frequency_plan_size == 0)
) {
common_attr->scan_active = false;
return -EINVAL;
}
/* Check frequency plan contents */
max_tuner_freq = common_attr->tuner_max_freq_rf;
min_tuner_freq = common_attr->tuner_min_freq_rf;
for (i = 0; i < (scan_param->frequency_plan_size); i++) {
s32 width = 0;
s32 step = scan_param->frequency_plan[i].step;
s32 first_freq = scan_param->frequency_plan[i].first;
s32 last_freq = scan_param->frequency_plan[i].last;
s32 min_freq = 0;
s32 max_freq = 0;
if (step <= 0) {
/* Step must be positive and non-zero */
common_attr->scan_active = false;
return -EINVAL;
}
if (first_freq > last_freq) {
/* First center frequency is higher than last center frequency */
common_attr->scan_active = false;
return -EINVAL;
}
width = last_freq - first_freq;
if ((width % step) != 0) {
/* Difference between last and first center frequency is not
an integer number of steps */
common_attr->scan_active = false;
return -EINVAL;
}
/* Check if frequency plan entry intersects with tuner range */
if (last_freq >= min_tuner_freq) {
if (first_freq <= max_tuner_freq) {
if (first_freq >= min_tuner_freq) {
min_freq = first_freq;
} else {
s32 n = 0;
n = (min_tuner_freq - first_freq) / step;
if (((min_tuner_freq - first_freq) % step) != 0)
n++;
min_freq = first_freq + n * step;
}
if (last_freq <= max_tuner_freq) {
max_freq = last_freq;
} else {
s32 n = 0;
n = (last_freq - max_tuner_freq) / step;
if (((last_freq - max_tuner_freq) % step) != 0)
n++;
max_freq = last_freq - n * step;
}
}
}
/* Keep track of total number of channels within tuner range
in this frequency plan. */
if ((min_freq != 0) && (max_freq != 0)) {
nr_channels_in_plan +=
(u16) (((max_freq - min_freq) / step) + 1);
/* Determine first frequency (within tuner range) to scan */
if (common_attr->scan_next_frequency == 0) {
common_attr->scan_next_frequency = min_freq;
common_attr->scan_freq_plan_index = i;
}
}
} /* for ( ... ) */
if (nr_channels_in_plan == 0) {
/* Tuner range and frequency plan ranges do not overlap */
common_attr->scan_active = false;
return -EIO;
}
/* Store parameters */
common_attr->scan_ready = false;
common_attr->scan_max_channels = nr_channels_in_plan;
common_attr->scan_channels_scanned = 0;
common_attr->scan_param = scan_param; /* SCAN_NEXT is now allowed */
scan_context = get_scan_context(demod, scan_context);
/*
* FIXME: Should we really ignore the result of the scan function?
*/
(*(get_scan_function(demod)))(scan_context, DRX_SCAN_COMMAND_INIT, NULL, NULL);
common_attr->scan_active = false;
return 0;
}
/*============================================================================*/
/**
* \brief Stop scanning.
* \param demod: Pointer to demodulator instance.
* \return int.
* \retval 0: Scan stopped.
* \retval -EIO: Something went wrong.
* \retval -EINVAL: Wrong parameters.
*/
static int ctrl_scan_stop(struct drx_demod_instance *demod)
{
int status = -EIO;
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
void *scan_context = NULL;
common_attr = (struct drx_common_attr *) demod->my_common_attr;
common_attr->scan_active = true;
if ((common_attr->scan_param == NULL) ||
(common_attr->scan_max_channels == 0)) {
/* Scan was not running, just return OK */
common_attr->scan_active = false;
return 0;
}
/* Call default or device-specific scanning stop function */
scan_context = get_scan_context(demod, scan_context);
status = (*(get_scan_function(demod)))
(scan_context, DRX_SCAN_COMMAND_STOP, NULL, NULL);
/* All done, invalidate scan-init */
common_attr->scan_param = NULL;
common_attr->scan_max_channels = 0;
common_attr->scan_active = false;
return status;
}
/*============================================================================*/
/**
* \brief Scan for next channel.
* \param demod: Pointer to demodulator instance.
* \param scan_progress: Pointer to scan progress.
* \return int.
* \retval 0: Channel found, DRX_CTRL_GET_CHANNEL can be used
* to retrieve channel parameters.
* \retval -EBUSY: Tried part of the channels, as specified in
* num_tries field of scan parameters. At least one
* more call to DRX_CTRL_SCAN_NEXT is needed to
* complete scanning.
* \retval -ERANGE: Reached end of scan range.
* \retval -EIO: Something went wrong.
* \retval -EINVAL: Wrong parameters. The scan_progress may be NULL.
*
* Progress indication will run from 0 upto DRX_SCAN_MAX_PROGRESS during scan.
*
*/
static int ctrl_scan_next(struct drx_demod_instance *demod, u16 *scan_progress)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
bool *scan_ready = (bool *)(NULL);
u16 max_progress = DRX_SCAN_MAX_PROGRESS;
u32 num_tries = 0;
u32 i = 0;
common_attr = (struct drx_common_attr *) demod->my_common_attr;
/* Check scan parameters */
if (scan_progress == NULL) {
common_attr->scan_active = false;
return -EINVAL;
}
*scan_progress = 0;
common_attr->scan_active = true;
if ((common_attr->scan_param == NULL) ||
(common_attr->scan_max_channels == 0)) {
/* ctrl_scan_init() was not called succesfully before ctrl_scan_next() */
common_attr->scan_active = false;
return -EIO;
}
*scan_progress = (u16) (((common_attr->scan_channels_scanned) *
((u32) (max_progress))) /
(common_attr->scan_max_channels));
/* Scan */
num_tries = common_attr->scan_param->num_tries;
scan_ready = &(common_attr->scan_ready);
for (i = 0; ((i < num_tries) && (!(*scan_ready))); i++) {
struct drx_channel scan_channel = { 0 };
int status = -EIO;
struct drx_frequency_plan *freq_plan = (struct drx_frequency_plan *) (NULL);
bool next_channel = false;
void *scan_context = NULL;
/* Next channel to scan */
freq_plan =
&(common_attr->scan_param->
frequency_plan[common_attr->scan_freq_plan_index]);
scan_channel.frequency = common_attr->scan_next_frequency;
scan_channel.bandwidth = freq_plan->bandwidth;
scan_channel.mirror = DRX_MIRROR_AUTO;
scan_channel.constellation = DRX_CONSTELLATION_AUTO;
scan_channel.hierarchy = DRX_HIERARCHY_AUTO;
scan_channel.priority = DRX_PRIORITY_HIGH;
scan_channel.coderate = DRX_CODERATE_AUTO;
scan_channel.guard = DRX_GUARD_AUTO;
scan_channel.fftmode = DRX_FFTMODE_AUTO;
scan_channel.classification = DRX_CLASSIFICATION_AUTO;
scan_channel.symbolrate = 0;
scan_channel.interleavemode = DRX_INTERLEAVEMODE_AUTO;
scan_channel.ldpc = DRX_LDPC_AUTO;
scan_channel.carrier = DRX_CARRIER_AUTO;
scan_channel.framemode = DRX_FRAMEMODE_AUTO;
scan_channel.pilot = DRX_PILOT_AUTO;
/* Call default or device-specific scanning function */
scan_context = get_scan_context(demod, scan_context);
status = (*(get_scan_function(demod)))
(scan_context, DRX_SCAN_COMMAND_NEXT, &scan_channel,
&next_channel);
/* Proceed to next channel if requested */
if (next_channel) {
int next_status = -EIO;
s32 skip = 0;
if (status == 0) {
/* a channel was found, so skip some frequency steps */
skip = common_attr->scan_param->skip;
}
next_status = scan_prepare_next_scan(demod, skip);
/* keep track of progress */
*scan_progress =
(u16) (((common_attr->scan_channels_scanned) *
((u32) (max_progress))) /
(common_attr->scan_max_channels));
if (next_status != 0) {
common_attr->scan_active = false;
return next_status;
}
}
if (status != -EBUSY) {
/* channel found or error */
common_attr->scan_active = false;
return status;
}
} /* for ( i = 0; i < ( ... num_tries); i++) */
if ((*scan_ready)) {
/* End of scan reached: call stop-scan, ignore any error */
ctrl_scan_stop(demod);
common_attr->scan_active = false;
return -ERANGE;
}
common_attr->scan_active = false;
return -EBUSY;
}
#endif /* #ifndef DRX_EXCLUDE_SCAN */
/*============================================================================*/
/**
* \brief Program tuner.
* \param demod: Pointer to demodulator instance.
* \param tunerChannel: Pointer to tuning parameters.
* \return int.
* \retval 0: Tuner programmed successfully.
* \retval -EIO: Something went wrong.
* \retval -EINVAL: Wrong parameters.
*
* tunerChannel passes parameters to program the tuner,
* but also returns the actual RF and IF frequency from the tuner.
*
*/
static int
ctrl_program_tuner(struct drx_demod_instance *demod, struct drx_channel *channel)
{
struct drx_common_attr *common_attr = (struct drx_common_attr *) (NULL);
enum drx_standard standard = DRX_STANDARD_UNKNOWN;
u32 tuner_mode = 0;
int status = -EIO;
s32 if_frequency = 0;
bool tuner_slow_mode = false;
/* can't tune without a tuner */
if (demod->my_tuner == NULL) {
return -EINVAL;
}
common_attr = (struct drx_common_attr *) demod->my_common_attr;
/* select analog or digital tuner mode based on current standard */
if (drx_ctrl(demod, DRX_CTRL_GET_STANDARD, &standard) != 0) {
return -EIO;
}
if (DRX_ISATVSTD(standard)) {
tuner_mode |= TUNER_MODE_ANALOG;
} else { /* note: also for unknown standard */
tuner_mode |= TUNER_MODE_DIGITAL;
}
/* select tuner bandwidth */
switch (channel->bandwidth) {
case DRX_BANDWIDTH_6MHZ:
tuner_mode |= TUNER_MODE_6MHZ;
break;
case DRX_BANDWIDTH_7MHZ:
tuner_mode |= TUNER_MODE_7MHZ;
break;
case DRX_BANDWIDTH_8MHZ:
tuner_mode |= TUNER_MODE_8MHZ;
break;
default: /* note: also for unknown bandwidth */
return -EINVAL;
}
tuner_slow_mode = DRX_ATTR_TUNERSLOWMODE(demod);
/* select fast (switch) or slow (lock) tuner mode */
if (tuner_slow_mode) {
tuner_mode |= TUNER_MODE_LOCK;
} else {
tuner_mode |= TUNER_MODE_SWITCH;
}
if (common_attr->tuner_port_nr == 1) {
bool bridge_closed = true;
int status_bridge = -EIO;
status_bridge =
drx_ctrl(demod, DRX_CTRL_I2C_BRIDGE, &bridge_closed);
if (status_bridge != 0) {
return status_bridge;
}
}
status = drxbsp_tuner_set_frequency(demod->my_tuner,
tuner_mode, channel->frequency);
/* attempt restoring bridge before checking status of set_frequency */
if (common_attr->tuner_port_nr == 1) {
bool bridge_closed = false;
int status_bridge = -EIO;
status_bridge =
drx_ctrl(demod, DRX_CTRL_I2C_BRIDGE, &bridge_closed);
if (status_bridge != 0) {
return status_bridge;
}
}
/* now check status of drxbsp_tuner_set_frequency */
if (status != 0) {
return status;
}
/* get actual RF and IF frequencies from tuner */
status = drxbsp_tuner_get_frequency(demod->my_tuner,
tuner_mode,
&(channel->frequency),
&(if_frequency));
if (status != 0) {
return status;
}
/* update common attributes with information available from this function;
TODO: check if this is required and safe */
DRX_ATTR_INTERMEDIATEFREQ(demod) = if_frequency;
return 0;
}
/*============================================================================*/
/**
* \brief function to do a register dump.
* \param demod: Pointer to demodulator instance.
* \param registers: Registers to dump.
* \return int.
* \retval 0: Dump executed successfully.
* \retval -EIO: Something went wrong.
* \retval -EINVAL: Wrong parameters.
*
*/
static int ctrl_dump_registers(struct drx_demod_instance *demod,
struct drx_reg_dump *registers)
{
u16 i = 0;
if (registers == NULL) {
/* registers not supplied */
return -EINVAL;
}
/* start dumping registers */
while (registers[i].address != 0) {
int status = -EIO;
u16 value = 0;
u32 data = 0;
status =
demod->my_access_funct->read_reg16func(demod->my_i2c_dev_addr,
registers[i].address,
&value, 0);
data = (u32) value;
if (status != 0) {
/* no breakouts;
depending on device ID, some HW blocks might not be available */
data |= ((u32) status) << 16;
}
registers[i].data = data;
i++;
}
/* all done, all OK (any errors are saved inside data) */
return 0;
}
/*============================================================================*/
/*============================================================================*/
/*===Microcode related functions==============================================*/
/*============================================================================*/
/*============================================================================*/
/**
* \brief Read a 16 bits word, expects big endian data.
* \param addr: Pointer to memory from which to read the 16 bits word.
* \return u16 The data read.
*
* This function takes care of the possible difference in endianness between the
* host and the data contained in the microcode image file.
*
*/
static u16 u_code_read16(u8 *addr)
{
/* Works fo any host processor */
u16 word = 0;
word = ((u16) addr[0]);
word <<= 8;
word |= ((u16) addr[1]);
return word;
}
/*============================================================================*/
/**
* \brief Read a 32 bits word, expects big endian data.
* \param addr: Pointer to memory from which to read the 32 bits word.
* \return u32 The data read.
*
* This function takes care of the possible difference in endianness between the
* host and the data contained in the microcode image file.
*
*/
static u32 u_code_read32(u8 *addr)
{
/* Works fo any host processor */
u32 word = 0;
word = ((u16) addr[0]);
word <<= 8;
word |= ((u16) addr[1]);
word <<= 8;
word |= ((u16) addr[2]);
word <<= 8;
word |= ((u16) addr[3]);
return word;
}
/*============================================================================*/
/**
* \brief Compute CRC of block of microcode data.
* \param block_data: Pointer to microcode data.
* \param nr_words: Size of microcode block (number of 16 bits words).
* \return u16 The computed CRC residu.
*/
static u16 u_code_compute_crc(u8 *block_data, u16 nr_words)
{
u16 i = 0;
u16 j = 0;
u32 crc_word = 0;
u32 carry = 0;
while (i < nr_words) {
crc_word |= (u32) u_code_read16(block_data);
for (j = 0; j < 16; j++) {
crc_word <<= 1;
if (carry != 0)
crc_word ^= 0x80050000UL;
carry = crc_word & 0x80000000UL;
}
i++;
block_data += (sizeof(u16));
}
return (u16)(crc_word >> 16);
}
/*============================================================================*/
/**
* \brief Handle microcode upload or verify.
* \param dev_addr: Address of device.
* \param mc_info: Pointer to information about microcode data.
* \param action: Either UCODE_UPLOAD or UCODE_VERIFY
* \return int.
* \retval 0:
* - In case of UCODE_UPLOAD: code is successfully uploaded.
* - In case of UCODE_VERIFY: image on device is equal to
* image provided to this control function.
* \retval -EIO:
* - In case of UCODE_UPLOAD: I2C error.
* - In case of UCODE_VERIFY: I2C error or image on device
* is not equal to image provided to this control function.
* \retval -EINVAL:
* - Invalid arguments.
* - Provided image is corrupt
*/
static int
ctrl_u_code(struct drx_demod_instance *demod,
struct drxu_code_info *mc_info, enum drxu_code_action action)
{
int rc;
u16 i = 0;
u16 mc_nr_of_blks = 0;
u16 mc_magic_word = 0;
u8 *mc_data = (u8 *)(NULL);
struct i2c_device_addr *dev_addr = (struct i2c_device_addr *)(NULL);
dev_addr = demod->my_i2c_dev_addr;
/* Check arguments */
if ((mc_info == NULL) || (mc_info->mc_data == NULL)) {
return -EINVAL;
}
mc_data = mc_info->mc_data;
/* Check data */
mc_magic_word = u_code_read16(mc_data);
mc_data += sizeof(u16);
mc_nr_of_blks = u_code_read16(mc_data);
mc_data += sizeof(u16);
if ((mc_magic_word != DRX_UCODE_MAGIC_WORD) || (mc_nr_of_blks == 0)) {
/* wrong endianess or wrong data ? */
return -EINVAL;
}
/* Scan microcode blocks first for version info if uploading */
if (action == UCODE_UPLOAD) {
/* Clear version block */
DRX_ATTR_MCRECORD(demod).aux_type = 0;
DRX_ATTR_MCRECORD(demod).mc_dev_type = 0;
DRX_ATTR_MCRECORD(demod).mc_version = 0;
DRX_ATTR_MCRECORD(demod).mc_base_version = 0;
for (i = 0; i < mc_nr_of_blks; i++) {
drxu_code_block_hdr_t block_hdr;
/* Process block header */
block_hdr.addr = u_code_read32(mc_data);
mc_data += sizeof(u32);
block_hdr.size = u_code_read16(mc_data);
mc_data += sizeof(u16);
block_hdr.flags = u_code_read16(mc_data);
mc_data += sizeof(u16);
block_hdr.CRC = u_code_read16(mc_data);
mc_data += sizeof(u16);
if (block_hdr.flags & 0x8) {
/* Aux block. Check type */
u8 *auxblk = mc_info->mc_data + block_hdr.addr;
u16 auxtype = u_code_read16(auxblk);
if (DRX_ISMCVERTYPE(auxtype)) {
DRX_ATTR_MCRECORD(demod).aux_type = u_code_read16(auxblk);
auxblk += sizeof(u16);
DRX_ATTR_MCRECORD(demod).mc_dev_type = u_code_read32(auxblk);
auxblk += sizeof(u32);
DRX_ATTR_MCRECORD(demod).mc_version = u_code_read32(auxblk);
auxblk += sizeof(u32);
DRX_ATTR_MCRECORD(demod).mc_base_version = u_code_read32(auxblk);
}
}
/* Next block */
mc_data += block_hdr.size * sizeof(u16);
}
/* After scanning, validate the microcode.
It is also valid if no validation control exists.
*/
rc = drx_ctrl(demod, DRX_CTRL_VALIDATE_UCODE, NULL);
if (rc != 0 && rc != -ENOTSUPP) {
return rc;
}
/* Restore data pointer */
mc_data = mc_info->mc_data + 2 * sizeof(u16);
}
/* Process microcode blocks */
for (i = 0; i < mc_nr_of_blks; i++) {
drxu_code_block_hdr_t block_hdr;
u16 mc_block_nr_bytes = 0;
/* Process block header */
block_hdr.addr = u_code_read32(mc_data);
mc_data += sizeof(u32);
block_hdr.size = u_code_read16(mc_data);
mc_data += sizeof(u16);
block_hdr.flags = u_code_read16(mc_data);
mc_data += sizeof(u16);
block_hdr.CRC = u_code_read16(mc_data);
mc_data += sizeof(u16);
/* Check block header on:
- data larger than 64Kb
- if CRC enabled check CRC
*/
if ((block_hdr.size > 0x7FFF) ||
(((block_hdr.flags & DRX_UCODE_CRC_FLAG) != 0) &&
(block_hdr.CRC != u_code_compute_crc(mc_data, block_hdr.size)))
) {
/* Wrong data ! */
return -EINVAL;
}
mc_block_nr_bytes = block_hdr.size * ((u16) sizeof(u16));
if (block_hdr.size != 0) {
/* Perform the desired action */
switch (action) {
/*================================================================*/
case UCODE_UPLOAD:
{
/* Upload microcode */
if (demod->my_access_funct->
write_block_func(dev_addr,
(dr_xaddr_t) block_hdr.
addr, mc_block_nr_bytes,
mc_data,
0x0000) !=
0) {
return -EIO;
} /* if */
}
break;
/*================================================================*/
case UCODE_VERIFY:
{
int result = 0;
u8 mc_data_buffer
[DRX_UCODE_MAX_BUF_SIZE];
u32 bytes_to_compare = 0;
u32 bytes_left_to_compare = 0;
u32 curr_addr = (dr_xaddr_t) 0;
u8 *curr_ptr = NULL;
bytes_left_to_compare = mc_block_nr_bytes;
curr_addr = block_hdr.addr;
curr_ptr = mc_data;
while (bytes_left_to_compare != 0) {
if (bytes_left_to_compare >
((u32)
DRX_UCODE_MAX_BUF_SIZE)) {
bytes_to_compare =
((u32)
DRX_UCODE_MAX_BUF_SIZE);
} else {
bytes_to_compare =
bytes_left_to_compare;
}
if (demod->my_access_funct->
read_block_func(dev_addr,
curr_addr,
(u16)
bytes_to_compare,
(u8 *)
mc_data_buffer,
0x0000) !=
0) {
return -EIO;
}
result =
drxbsp_hst_memcmp(curr_ptr,
mc_data_buffer,
bytes_to_compare);
if (result != 0) {
return -EIO;
}
curr_addr +=
((dr_xaddr_t)
(bytes_to_compare / 2));
curr_ptr =
&(curr_ptr[bytes_to_compare]);
bytes_left_to_compare -=
((u32) bytes_to_compare);
} /* while( bytes_to_compare > DRX_UCODE_MAX_BUF_SIZE ) */
}
break;
/*================================================================*/
default:
return -EINVAL;
break;
} /* switch ( action ) */
}
/* if (block_hdr.size != 0 ) */
/* Next block */
mc_data += mc_block_nr_bytes;
} /* for( i = 0 ; i<mc_nr_of_blks ; i++ ) */
return 0;
}
/*============================================================================*/
/**
* \brief Build list of version information.
* \param demod: A pointer to a demodulator instance.
* \param version_list: Pointer to linked list of versions.
* \return int.
* \retval 0: Version information stored in version_list
* \retval -EINVAL: Invalid arguments.
*/
static int
ctrl_version(struct drx_demod_instance *demod, struct drx_version_list **version_list)
{
static char drx_driver_core_module_name[] = "Core driver";
static char drx_driver_core_version_text[] =
DRX_VERSIONSTRING(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
static struct drx_version drx_driver_core_version;
static struct drx_version_list drx_driver_core_version_list;
struct drx_version_list *demod_version_list = (struct drx_version_list *) (NULL);
int return_status = -EIO;
/* Check arguments */
if (version_list == NULL) {
return -EINVAL;
}
/* Get version info list from demod */
return_status = (*(demod->my_demod_funct->ctrl_func)) (demod,
DRX_CTRL_VERSION,
(void *)
&demod_version_list);
/* Always fill in the information of the driver SW . */
drx_driver_core_version.module_type = DRX_MODULE_DRIVERCORE;
drx_driver_core_version.module_name = drx_driver_core_module_name;
drx_driver_core_version.v_major = VERSION_MAJOR;
drx_driver_core_version.v_minor = VERSION_MINOR;
drx_driver_core_version.v_patch = VERSION_PATCH;
drx_driver_core_version.v_string = drx_driver_core_version_text;
drx_driver_core_version_list.version = &drx_driver_core_version;
drx_driver_core_version_list.next = (struct drx_version_list *) (NULL);
if ((return_status == 0) && (demod_version_list != NULL)) {
/* Append versioninfo from driver to versioninfo from demod */
/* Return version info in "bottom-up" order. This way, multiple
devices can be handled without using malloc. */
struct drx_version_list *current_list_element = demod_version_list;
while (current_list_element->next != NULL) {
current_list_element = current_list_element->next;
}
current_list_element->next = &drx_driver_core_version_list;
*version_list = demod_version_list;
} else {
/* Just return versioninfo from driver */
*version_list = &drx_driver_core_version_list;
}
return 0;
}
/*============================================================================*/
/*============================================================================*/
/*== Exported functions ======================================================*/
/*============================================================================*/
/*============================================================================*/
/**
* \brief This function is obsolete.
* \param demods: Don't care, parameter is ignored.
* \return int Return status.
* \retval 0: Initialization completed.
*
* This function is obsolete, prototype available for backward compatability.
*
*/
int drx_init(struct drx_demod_instance *demods[])
{
return 0;
}
/*============================================================================*/
/**
* \brief This function is obsolete.
* \return int Return status.
* \retval 0: Terminated driver successful.
*
* This function is obsolete, prototype available for backward compatability.
*
*/
int drx_term(void)
{
return 0;
}
/*============================================================================*/
/**
* \brief Open a demodulator instance.
* \param demod: A pointer to a demodulator instance.
* \return int Return status.
* \retval 0: Opened demod instance with succes.
* \retval -EIO: Driver not initialized or unable to initialize
* demod.
* \retval -EINVAL: Demod instance has invalid content.
*
*/
int drx_open(struct drx_demod_instance *demod)
{
int status = 0;
if ((demod == NULL) ||
(demod->my_demod_funct == NULL) ||
(demod->my_common_attr == NULL) ||
(demod->my_ext_attr == NULL) ||
(demod->my_i2c_dev_addr == NULL) ||
(demod->my_common_attr->is_opened)) {
return -EINVAL;
}
status = (*(demod->my_demod_funct->open_func)) (demod);
if (status == 0)
demod->my_common_attr->is_opened = true;
return status;
}
/*============================================================================*/
/**
* \brief Close device.
* \param demod: A pointer to a demodulator instance.
* \return int Return status.
* \retval 0: Closed demod instance with succes.
* \retval -EIO: Driver not initialized or error during close
* demod.
* \retval -EINVAL: Demod instance has invalid content.
*
* Free resources occupied by device instance.
* Put device into sleep mode.
*/
int drx_close(struct drx_demod_instance *demod)
{
int status = 0;
if ((demod == NULL) ||
(demod->my_demod_funct == NULL) ||
(demod->my_common_attr == NULL) ||
(demod->my_ext_attr == NULL) ||
(demod->my_i2c_dev_addr == NULL) ||
(!demod->my_common_attr->is_opened)) {
return -EINVAL;
}
status = (*(demod->my_demod_funct->close_func)) (demod);
DRX_ATTR_ISOPENED(demod) = false;
return status;
}
/*============================================================================*/
/**
* \brief Control the device.
* \param demod: A pointer to a demodulator instance.
* \param ctrl: Reference to desired control function.
* \param ctrl_data: Pointer to data structure for control function.
* \return int Return status.
* \retval 0: Control function completed successfully.
* \retval -EIO: Driver not initialized or error during
* control demod.
* \retval -EINVAL: Demod instance or ctrl_data has invalid
* content.
* \retval -ENOTSUPP: Specified control function is not
* available.
*
* Data needed or returned by the control function is stored in ctrl_data.
*
*/
int
drx_ctrl(struct drx_demod_instance *demod, u32 ctrl, void *ctrl_data)
{
int status = -EIO;
if ((demod == NULL) ||
(demod->my_demod_funct == NULL) ||
(demod->my_common_attr == NULL) ||
(demod->my_ext_attr == NULL) || (demod->my_i2c_dev_addr == NULL)
) {
return -EINVAL;
}
if (((!demod->my_common_attr->is_opened) &&
(ctrl != DRX_CTRL_PROBE_DEVICE) && (ctrl != DRX_CTRL_VERSION))
) {
return -EINVAL;
}
if ((DRX_ISPOWERDOWNMODE(demod->my_common_attr->current_power_mode) &&
(ctrl != DRX_CTRL_POWER_MODE) &&
(ctrl != DRX_CTRL_PROBE_DEVICE) &&
(ctrl != DRX_CTRL_NOP) && (ctrl != DRX_CTRL_VERSION)
)
) {
return -ENOTSUPP;
}
/* Fixed control functions */
switch (ctrl) {
/*======================================================================*/
case DRX_CTRL_NOP:
/* No operation */
return 0;
break;
/*======================================================================*/
case DRX_CTRL_VERSION:
return ctrl_version(demod, (struct drx_version_list **)ctrl_data);
break;
/*======================================================================*/
default:
/* Do nothing */
break;
}
/* Virtual functions */
/* First try calling function from derived class */
status = (*(demod->my_demod_funct->ctrl_func)) (demod, ctrl, ctrl_data);
if (status == -ENOTSUPP) {
/* Now try calling a the base class function */
switch (ctrl) {
/*===================================================================*/
case DRX_CTRL_LOAD_UCODE:
return ctrl_u_code(demod,
(struct drxu_code_info *)ctrl_data,
UCODE_UPLOAD);
break;
/*===================================================================*/
case DRX_CTRL_VERIFY_UCODE:
{
return ctrl_u_code(demod,
(struct drxu_code_info *)ctrl_data,
UCODE_VERIFY);
}
break;
#ifndef DRX_EXCLUDE_SCAN
/*===================================================================*/
case DRX_CTRL_SCAN_INIT:
{
return ctrl_scan_init(demod,
(struct drx_scan_param *) ctrl_data);
}
break;
/*===================================================================*/
case DRX_CTRL_SCAN_NEXT:
{
return ctrl_scan_next(demod, (u16 *)ctrl_data);
}
break;
/*===================================================================*/
case DRX_CTRL_SCAN_STOP:
{
return ctrl_scan_stop(demod);
}
break;
#endif /* #ifndef DRX_EXCLUDE_SCAN */
/*===================================================================*/
case DRX_CTRL_PROGRAM_TUNER:
{
return ctrl_program_tuner(demod,
(struct drx_channel *)
ctrl_data);
}
break;
/*===================================================================*/
case DRX_CTRL_DUMP_REGISTERS:
{
return ctrl_dump_registers(demod,
(struct drx_reg_dump *)
ctrl_data);
}
break;
/*===================================================================*/
default:
return -ENOTSUPP;
}
} else {
return status;
}
return 0;
}
/*============================================================================*/
/* END OF FILE */