77f8c9c9f1
This was broken when the major update to iap was comitted. ia-lingo7.c created and various iap related files modified. On 4G, 6G and Nano 1/2Gen iPods the remote will function even though the radio won't. Tested on 4G Greyscale, 4G Color, 4G Photo, 4G Mini 1st Gen, 4G Mini 2Gen, Nano 1G, Nano 2G, Video 5G, Video 5.5G Change-Id: Ia74e3d07d9ab5edc6da8eafa96801ede722be331
464 lines
12 KiB
C
464 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
* tuner for the ipod fm remote and other ipod remote tuners
|
|
*
|
|
* Copyright (C) 2009 Laurent Gautier
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "kernel.h"
|
|
#include "iap.h"
|
|
#include "tuner.h" /* tuner abstraction interface */
|
|
#include "adc.h"
|
|
#include "settings.h"
|
|
#include "power.h"
|
|
#include "rds.h"
|
|
|
|
static unsigned char tuner_param = 0x00, old_tuner_param = 0xFF;
|
|
/* temp var for tests to avoid looping execution in submenus settings*/
|
|
static int mono_mode = -1, old_region = -1;
|
|
|
|
int radio_present = 0;
|
|
|
|
static int tuner_frequency = 0;
|
|
static int tuner_signal_power = 0;
|
|
static bool radio_tuned = false;
|
|
|
|
static void rmt_tuner_signal_power(unsigned char value)
|
|
{
|
|
tuner_signal_power = (int)(value);
|
|
}
|
|
|
|
void rmt_tuner_freq(unsigned int len, const unsigned char *buf)
|
|
{
|
|
/* length currently unused */
|
|
(void)len;
|
|
|
|
unsigned int khz = (buf[2] << 24) | (buf[3] << 16) |
|
|
(buf[4] << 8) | buf[5];
|
|
tuner_frequency = khz *1000 ;
|
|
radio_tuned = true;
|
|
rmt_tuner_signal_power(buf[6]);
|
|
}
|
|
|
|
static void rmt_tuner_set_freq(int curr_freq)
|
|
{
|
|
if (curr_freq != tuner_frequency)
|
|
{
|
|
radio_tuned = false;
|
|
tuner_signal_power = 0;
|
|
/* clear rds data */
|
|
rds_reset();
|
|
/* ex: 00 01 63 14 = 90.9MHz */
|
|
unsigned char data[] = {0x07, 0x0B, 0x00, 0x01, 0x63, 0x14};
|
|
|
|
if (curr_freq != 0)
|
|
{
|
|
unsigned int khz = curr_freq / 1000;
|
|
data[2] = (khz >> 24) & 0xFF;
|
|
data[3] = (khz >> 16) & 0xFF;
|
|
data[4] = (khz >> 8) & 0xFF;
|
|
data[5] = (khz >> 0) & 0xFF;
|
|
iap_send_pkt(data, sizeof(data));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rmt_tuner_sleep(int state)
|
|
{
|
|
if (state == 0)
|
|
{
|
|
rds_init();
|
|
tuner_param = 0x00;
|
|
old_tuner_param = 0xFF;
|
|
mono_mode = -1;
|
|
old_region = -1;
|
|
tuner_frequency = 0;
|
|
radio_tuned = false;
|
|
|
|
/* tuner HW on */
|
|
const unsigned char data[] = {0x07, 0x05, 0x01};
|
|
iap_send_pkt(data, sizeof(data));
|
|
/* set rds on */
|
|
const unsigned char data3[] = {0x07, 0x20, 0x40, 0x00, 0x00, 0x10 };
|
|
iap_send_pkt(data3, sizeof(data3));
|
|
/* boost gain */
|
|
const unsigned char data1[] = {0x07, 0x24, 0x06 };
|
|
iap_send_pkt(data1, sizeof(data1));
|
|
/* tuner mode */
|
|
const unsigned char data4[] = {0x07, 0x0E, 0x00 };
|
|
iap_send_pkt(data4, sizeof(data3));
|
|
/* set volume */
|
|
unsigned char data2[] = {0x03, 0x09, 0x04, 0x00, 0x00 };
|
|
data2[4] = (char)((global_settings.volume+58) * 4);
|
|
iap_send_pkt(data2, sizeof(data2));
|
|
}
|
|
else
|
|
{
|
|
/* unbooste gain */
|
|
const unsigned char data[] = {0x07, 0x24, 0x00};
|
|
iap_send_pkt(data, sizeof(data));
|
|
/* set rds off */
|
|
const unsigned char data1[] = {0x07, 0x20, 0x00, 0x00, 0x00, 0x00 };
|
|
iap_send_pkt(data1, sizeof(data1));
|
|
/* stop tuner HW */
|
|
const unsigned char data2[] = {0x07, 0x05, 0x00};
|
|
iap_send_pkt(data2, sizeof(data2));
|
|
}
|
|
}
|
|
|
|
void rmt_tuner_scan(int param)
|
|
{
|
|
const unsigned char data[] = {0x07, 0x11, 0x08}; /* RSSI level */
|
|
unsigned char updown = 0x00;
|
|
radio_tuned = false;
|
|
iap_send_pkt(data, sizeof(data));
|
|
|
|
if (param == 1)
|
|
{
|
|
updown = 0x07; /* scan up */
|
|
}
|
|
else if (param == -1)
|
|
{
|
|
updown = 0x08; /* scan down */
|
|
}
|
|
else if (param == 10)
|
|
{
|
|
updown = 0x01; /* scan up starting from beginning of the band */
|
|
}
|
|
unsigned char data1[] = {0x07, 0x12, updown};
|
|
iap_send_pkt(data1, sizeof(data1));
|
|
}
|
|
|
|
static void rmt_tuner_mute(int value)
|
|
{
|
|
/* mute flag off (play) */
|
|
/* The Apple Tuner does NOT appear to support muting. The Apple
|
|
* firmware turns the power off when pressing pause on the iPod
|
|
* or on the Tuner Remote.
|
|
*/
|
|
if (value)
|
|
{
|
|
/* mute flag on (pause) */
|
|
unsigned char data[] = {0x03, 0x09, 0x03, 0x02};
|
|
iap_send_pkt(data, sizeof(data));
|
|
rmt_tuner_sleep(1);
|
|
}
|
|
else
|
|
{
|
|
unsigned char data[] = {0x03, 0x09, 0x03, 0x01};
|
|
iap_send_pkt(data, sizeof(data));
|
|
rmt_tuner_sleep(0);
|
|
}
|
|
}
|
|
|
|
static void rmt_tuner_region(int region)
|
|
{
|
|
if (region != old_region)
|
|
{
|
|
const struct fm_region_data *rd = &fm_region_data[region];
|
|
unsigned char data[] = {0x07, 0x08, 0x00};
|
|
/* Apple MFi Accessory Firmware Spec R46 now lists
|
|
* the following bands
|
|
* ID00 AM 520-1710Khz Not Supported
|
|
* ID02 Japan 76-90Mkz 100Khz 50/75uS
|
|
* ID01 87.5-108Mhz US 200Khz 75uS, EU 100Kz 50uS
|
|
* ID03 76-108Mhz Wideband. Not Supported
|
|
*/
|
|
if (rd->freq_min == 76000000)
|
|
{
|
|
data[2] = 0x02; /* japan band */
|
|
}
|
|
else
|
|
{
|
|
data[2] = 0x01; /* us/europe band */
|
|
}
|
|
iap_send_pkt(data, sizeof(data));
|
|
sleep(HZ/100);
|
|
old_region = region;
|
|
}
|
|
}
|
|
|
|
/* set stereo/mono, deemphasis, delta freq... */
|
|
static void rmt_tuner_set_param(unsigned char tuner_param)
|
|
{
|
|
if(tuner_param != old_tuner_param)
|
|
{
|
|
unsigned char data[] = {0x07, 0x0E, 0x00};
|
|
|
|
data[2] = tuner_param;
|
|
iap_send_pkt(data, sizeof(data));
|
|
old_tuner_param = tuner_param;
|
|
}
|
|
}
|
|
|
|
static void set_deltafreq(int delta)
|
|
{
|
|
tuner_param &= 0xFC;
|
|
switch (delta)
|
|
{
|
|
case 1:
|
|
{
|
|
/* 100KHz */
|
|
tuner_param |= 0x01;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
/* 50KHz */
|
|
tuner_param |= 0x02;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
/* 200KHz */
|
|
tuner_param |= 0x00;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void set_deemphasis(int deemphasis)
|
|
{
|
|
tuner_param &= 0xBF;
|
|
switch (deemphasis)
|
|
{
|
|
case 1:
|
|
{
|
|
tuner_param |= 0x40;
|
|
/* 50uS */
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
tuner_param |= 0x00;
|
|
/* 75uS */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void set_mono(int value)
|
|
{
|
|
tuner_param &= 0xEF;
|
|
|
|
if (value != mono_mode)
|
|
{
|
|
tuner_param &= 0xEF;
|
|
if (value == 1)
|
|
tuner_param |= 0x10;
|
|
rmt_tuner_set_param(tuner_param);
|
|
sleep(HZ/100);
|
|
mono_mode = value;
|
|
}
|
|
}
|
|
|
|
static bool reply_timeout(void)
|
|
{
|
|
int timeout = 0;
|
|
|
|
sleep(HZ/50);
|
|
do
|
|
{
|
|
sleep(HZ/50);
|
|
timeout++;
|
|
}
|
|
while((ipod_rmt_tuner_get(RADIO_TUNED) == 0) && (timeout < TIMEOUT_VALUE));
|
|
|
|
return (timeout >= TIMEOUT_VALUE);
|
|
}
|
|
|
|
void rmt_tuner_rds_data(unsigned int len, const unsigned char *buf)
|
|
{
|
|
if (buf[2] == 0x1E)
|
|
{
|
|
rds_push_info(RDS_INFO_PS, (uintptr_t)(buf+4), 8);
|
|
}
|
|
else if(buf[2] == 0x04)
|
|
{
|
|
rds_push_info(RDS_INFO_RT, (uintptr_t)(buf+4), len-4);
|
|
}
|
|
}
|
|
|
|
/* tuner abstraction layer: set something to the tuner */
|
|
int ipod_rmt_tuner_set(int setting, int value)
|
|
{
|
|
switch(setting)
|
|
{
|
|
case RADIO_SLEEP:
|
|
{
|
|
rmt_tuner_sleep(value);
|
|
sleep(HZ/10);
|
|
if(value)
|
|
{
|
|
tuner_frequency = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RADIO_FREQUENCY:
|
|
{
|
|
rmt_tuner_set_freq(value);
|
|
if (reply_timeout())
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
case RADIO_SCAN_FREQUENCY:
|
|
{
|
|
const struct fm_region_data * const fmr =
|
|
&fm_region_data[global_settings.fm_region];
|
|
|
|
/* case: scan for presets, back to beginning of the band */
|
|
if (radio_tuned && (value == fmr->freq_min))
|
|
{
|
|
tuner_set(RADIO_FREQUENCY,value);
|
|
}
|
|
|
|
/* scan through frequencies */
|
|
if (radio_tuned)
|
|
{
|
|
if ((tuner_frequency <= fmr->freq_min)
|
|
&& (tuner_frequency >= fmr->freq_max))
|
|
{
|
|
tuner_set(RADIO_FREQUENCY,value);
|
|
}
|
|
/* scan down */
|
|
if(value < tuner_frequency)
|
|
rmt_tuner_scan(-1);
|
|
/* scan up */
|
|
else
|
|
rmt_tuner_scan(1);
|
|
|
|
sleep(HZ/10);
|
|
if (reply_timeout())
|
|
{
|
|
tuner_set(RADIO_FREQUENCY,value);
|
|
rmt_tuner_scan(1);
|
|
if (reply_timeout() == true)
|
|
return 0;
|
|
}
|
|
radio_tuned = false;
|
|
}
|
|
|
|
if (tuner_frequency == value)
|
|
{
|
|
radio_tuned = true;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
radio_tuned = false;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
case RADIO_MUTE:
|
|
{
|
|
/* mute flag sent to accessory */
|
|
rmt_tuner_mute(value);
|
|
break;
|
|
}
|
|
|
|
case RADIO_REGION:
|
|
{
|
|
/* The latest MFi Accessory Firmware Document I have lists the
|
|
* following regions
|
|
* US 87.5-108Mhz 200Khz 75uS
|
|
* US/EU 87.5-108Mhz 100Khz 75/50uS
|
|
* JP 76.0-90Mhz 100Mhz 50/75uS
|
|
*
|
|
* with the following bands
|
|
* 0x00 AM WordlWide 520-1710Khz
|
|
* 0x01 FM EU 87.5-108.0Mhz
|
|
* 0x02 FM JP 76.0-90.0Mhz
|
|
* 0x03 FM Wide 76.0-108.0Mhz
|
|
*
|
|
*
|
|
* A 7G Classic with the latest Apple Firmware returns the following
|
|
* regions with the settings listed
|
|
* Americas 87.5-108 200Khz 75uS
|
|
* Asia 87.5-108 100Khz 75uS
|
|
* Australia 87.5-108 200Khz 75uS
|
|
* Europe 87.5-108 100Khz 75uS
|
|
* Japan 76.0-90. 100Kz 75uS
|
|
*/
|
|
const struct fm_region_data *rd = &fm_region_data[value];
|
|
int band = (rd->freq_min == 76000000) ? 2 : 0;
|
|
int spacing = (100000 / rd->freq_step);
|
|
int deemphasis = (rd->deemphasis == 50) ? 1 : 0;
|
|
|
|
rmt_tuner_region(band);
|
|
set_deltafreq(spacing);
|
|
set_deemphasis(deemphasis);
|
|
rmt_tuner_set_param(tuner_param);
|
|
break;
|
|
}
|
|
|
|
case RADIO_FORCE_MONO:
|
|
{
|
|
set_mono(value);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* tuner abstraction layer: read something from the tuner */
|
|
int ipod_rmt_tuner_get(int setting)
|
|
{
|
|
int val = -1; /* default for unsupported query */
|
|
|
|
switch(setting)
|
|
{
|
|
case RADIO_PRESENT:
|
|
val = radio_present;
|
|
if (val)
|
|
{
|
|
/* if accessory disconnected */
|
|
if(adc_read(ADC_ACCESSORY) >= 10)
|
|
{
|
|
radio_present = 0;
|
|
val = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* radio tuned: yes no */
|
|
case RADIO_TUNED:
|
|
val = 0;
|
|
if (radio_tuned)
|
|
val = 1;
|
|
break;
|
|
|
|
/* radio is always stereo */
|
|
/* we can't know when it's in mono mode, depending of signal quality */
|
|
/* except if it is forced in mono mode */
|
|
case RADIO_STEREO:
|
|
val = true;
|
|
break;
|
|
}
|
|
return val;
|
|
}
|