root/src/add-ons/media/media-add-ons/radeon/Tuner.cpp
/******************************************************************************
/
/       File:                   Tuner.cpp
/
/       Description:    Philips Desktop TV Tuners interface.
/
/       Copyright 2001, Carlos Hasan
/
*******************************************************************************/

#include <Debug.h>
#include "Tuner.h"

enum tuner_control_bits {
    CHANGE_PUMP             = 0x40,     /* change pump settings */
    CHANGE_PUMP_FAST        = 0x00,     /*  for fast tuning */
    CHANGE_PUMP_SLOW        = 0x40,     /*  for moderate speed tuning */

    TEST_MODE               = 0x38,     /* test mode settings */
    TEST_MODE_NORMAL        = 0x08,     /*  for normal operation */

    RATIO_SELECT            = 0x06,     /* ratio select */
    RATIO_SELECT_FAST       = 0x00,     /*  fast picture search */
    RATIO_SELECT_SLOW       = 0x02,     /*  slow picture search */
    RATIO_SELECT_NORMAL     = 0x06,     /*  normal picture search */

    OS_MODE                 = 0x01,     /* PLL disabling */
    OS_MODE_NORMAL          = 0x00,     /*  for normal operation */
    OS_MODE_SWITCH          = 0x01      /*  change pump to high impedance */
};

enum tuner_status_bits {
    STB_POR                 = 0x80,     /* power on reset */
    STB_FL                  = 0x40,     /* in-lock flag */
    STB_INF                 = 0x38,     /* digital information */
    STB_ADC                 = 0x07      /* A/D converter */
};

/*****************************************************************************
/
/   Tuner Parameters and Frequencies for TV Antenna and Cable Channels
/
******************************************************************************/

static const int kBandsPerTuner   = 5;

static const struct {
    tuner_type brand;
    const char* name;
    struct {
        int pll;
        float minFrequency, maxFrequency;
    } bands[kBandsPerTuner];
} kTunerTable[] = {
    /* Philips FI1236 NTSC M/N */
    { C_TUNER_FI1236, "FI1236 NTSC",
      { { 0xa2,  54.00, 157.25 },
        { 0x94, 162.00, 451.25 },
        { 0x34, 451.25, 463.25 },
        { 0x31, 463.25, 801.25 },
        { 0x00,   0.00,   0.00 } } },

    /* Philips FI1236J NTSC Japan */
    { C_TUNER_FI1236J, "FI1236J NTSC Japan",
      { { 0xa2,  54.00, 157.25 },
        { 0x94, 162.00, 451.25 },
        { 0x34, 451.25, 463.25 },
        { 0x31, 463.25, 801.25 },
        { 0x00,   0.00,   0.00 } } },

    /* Philips FI1236MK2 NTSC M/N */
    { C_TUNER_FI1236MK2, "FI1236MK2 NTSC",
      { { 0xa0,  55.25, 160.00 },
        { 0x90, 160.00, 454.00 },
        { 0x30, 454.00, 855.25 },
        { 0x00,   0.00,   0.00 },
        { 0x00,   0.00,   0.00 } } },

    /* Philips FI1216 PAL B/G */
    { C_TUNER_FI1216, "FI1216 PAL",
      { { 0xa2,  45.00, 140.25 },
        { 0xa4, 140.25, 168.25 },
        { 0x94, 168.25, 447.25 },
        { 0x34, 447.25, 463.25 },
        { 0x31, 463.25, 855.25 } } },

    /* Philips FI1216MK2 PAL B/G */
    { C_TUNER_FI1216MK2, "FI1216MK2 PAL",
      { { 0xa0,  48.25, 170.00 },
        { 0x90, 170.00, 450.00 },
        { 0x30, 450.00, 855.25 },
        { 0x00,   0.00,   0.00 },
        { 0x00,   0.00,   0.00 } } },

    /* Philips FI1216MF PAL B/G, SECAM L/L' */
    { C_TUNER_FI1216MF, "FI1216MF PAL/SECAM",
      { { 0xa1,  45.00, 168.25 },
        { 0x91, 168.25, 447.25 },
        { 0x31, 447.25, 855.25 },
        { 0x00,   0.00,   0.00 },
        { 0x00,   0.00,   0.00 } } },

    /* Philips FI1246 PAL I */
    { C_TUNER_FI1246, "FI1246 PAL I",
      { { 0xa2,  45.00, 140.25 },
        { 0xa4, 140.25, 168.25 },
        { 0x94, 168.25, 447.25 },
        { 0x34, 447.25, 463.25 },
        { 0x31, 463.25, 855.25 } } },

    /* Philips FI1256 SECAM D/K */
    { C_TUNER_FI1256, "FI1256 SECAM",
      { { 0xa0,  48.25, 168.25 },
        { 0x90, 175.25, 455.25 },
        { 0x30, 463.25, 863.25 },
        { 0x00,   0.00,   0.00 },
        { 0x00,   0.00,   0.00 } } },

    /* Temic FN5AL PAL I/B/G/DK, SECAM DK */
    { C_TUNER_TEMIC_FN5AL_PAL, "Temic FN5AL PAL",
      { { 0xa2,  45.00, 140.25 },
        { 0xa4, 140.25, 168.25 },
        { 0x94, 168.25, 447.25 },
        { 0x31, 447.25, 855.25 },
        { 0x00,   0.00,   0.00 } } },

    /* Temic FN5AL PAL I/B/G/DK, SECAM DK */
    { C_TUNER_TEMIC_FN5AL_SECAM, "Temic FN5AL SECAM",
      { { 0xa0,  48.25, 168.25 },
        { 0x90, 175.25, 455.25 },
        { 0x30, 463.25, 863.25 },
        { 0x00,   0.00,   0.00 },
        { 0x00,   0.00,   0.00 } } } };

static const int kNumTuners = sizeof(kTunerTable) / sizeof(kTunerTable[0]);


CTuner::CTuner(CI2CPort & port)
        :       fPort(port),
                fType(C_TUNER_NONE),
                fAddress(0xC6),
                fDivider(0)
{
        PRINT(("CTuner::CTuner()\n"));
        
        if( fPort.InitCheck() == B_OK ) {
                radeon_video_tuner tuner;
                radeon_video_decoder video;
                radeon_video_clock clock;
                int dummy;
                
                fPort.Radeon().GetMMParameters(tuner, video, clock, dummy, dummy, dummy);
                switch (tuner) {
                case C_RADEON_NO_TUNER:
                        fType = C_TUNER_NONE;
                        break;
                case C_RADEON_FI1236_MK1_NTSC:
                        fType = C_TUNER_FI1236;
                        break;
                case C_RADEON_FI1236_MK2_NTSC_JAPAN:
                        fType = C_TUNER_FI1236J;
                        break;
                case C_RADEON_FI1216_MK2_PAL_BG:
                        fType = C_TUNER_FI1216;
                        break;
                case C_RADEON_FI1246_MK2_PAL_I:
                        fType = C_TUNER_FI1246;
                        break;
                case C_RADEON_FI1216_MF_MK2_PAL_BG_SECAM_L:
                        fType = C_TUNER_FI1216MF;
                        break;
                case C_RADEON_FI1236_MK2_NTSC:
                        fType = C_TUNER_FI1236MK2;
                        break;
                case C_RADEON_FI1256_MK2_SECAM_DK:
                        fType = C_TUNER_FI1256;
                        break;
                case C_RADEON_FI1216_MK2_PAL_BG_SECAM_L:
                        fType = C_TUNER_FI1216MK2;
                        break;
                case C_RADEON_TEMIC_FN5AL_PAL_IBGDK_SECAM_DK:
                        fType = C_TUNER_TEMIC_FN5AL_PAL;
                        break;
                default:
                        fType = C_TUNER_FI1236;
                        break;
                }
        
            for (fAddress = 0xc0; fAddress <= 0xc6; fAddress += 0x02) {
                if (fPort.Probe(fAddress)) {
                        PRINT(("CTuner::CTuner() - TV Tuner found at I2C port 0x%02x\n", fAddress));
                                break;
                        }
            }
        }
        if( InitCheck() != B_OK )
                PRINT(("CTuner::CTuner() - TV Tuner not found!\n"));
}

CTuner::~CTuner()
{
        PRINT(("CTuner::~CTuner()\n"));
}

status_t CTuner::InitCheck() const
{
    return (fType != C_TUNER_NONE && fAddress >= 0xc0 && fAddress <= 0xc6) ? 
        B_OK : B_ERROR;
}

bool CTuner::SetFrequency(float frequency, float picture)
{
        //PRINT(("CTuner::SetFrequency(%g, %g)\n", frequency, picture));

    for (int index = 0; index < kNumTuners; index++) {
        if (kTunerTable[index].brand == fType) {
            for (int band = 0; band < kBandsPerTuner; band++) {
                if (frequency >= kTunerTable[index].bands[band].minFrequency &&
                    frequency <= kTunerTable[index].bands[band].maxFrequency) {
                    SetParameters(
                        (int) (16.0 * (frequency + picture)),
                        CHANGE_PUMP_FAST | RATIO_SELECT_NORMAL |
                        TEST_MODE_NORMAL | OS_MODE_NORMAL,
                        kTunerTable[index].bands[band].pll |
                        ((Status() & STB_INF) >> 3));
                    return true;
                }
            }
        }
    }
    return false;
}

bool CTuner::SweepFrequency(float frequency, float picture)
{
        PRINT(("CTuner::SweepFrequency(%g, %g)\n", frequency, picture));

    float centerFrequency = frequency;

    /* sweeping down */
    do {
        SetFrequency(frequency, picture);
        for (int timeout = 0; timeout < 10; timeout++) {
            snooze(20000);
            if (IsLocked())
                break;
            }
    } while (ADC() == 0  &&
             (frequency -= 0.5000) > centerFrequency - 3.000);

    /* sweeping up */
    do {
        SetFrequency(frequency, picture);
        for (int timeout = 0; timeout < 10; timeout++) {
            snooze(20000);
            if (IsLocked())
                break;
        }
    } while (ADC() != 0 &&
             (frequency += 0.0625) < centerFrequency + 0.500);

    return (ADC() == 0);
}

const char * CTuner::Name() const
{
    for (int index = 0; index < kNumTuners; index++) {
        if (kTunerTable[index].brand == fType)
            return kTunerTable[index].name;
    }
    return 0;
}

tuner_type CTuner::Type() const
{
        return fType;
}

bool CTuner::HasSignal(void)
{
    return (IsLocked() && ADC() <= 2);
}

int CTuner::Status(void)
{
    char buffer[1];
    fPort.Read(fAddress, buffer, sizeof(buffer));
    return buffer[0] & 0xff;
}

int CTuner::ADC(void)
{
    return Status() & STB_ADC;
}

bool CTuner::IsLocked(void)
{
    return (Status() & STB_FL) != 0;
}

void CTuner::SetParameters(int divider, int control, int band)
{
    char buffer[4];

    if (divider > fDivider) {
        buffer[0] = (divider >> 8) & 0x7f;
        buffer[1] = (divider >> 0) & 0xff;

        buffer[2] = (control | 0x80);
        buffer[3] = (band & 0xff);
    }
    else {
        buffer[0] = (control | 0x80);
        buffer[1] = (band & 0xff);

        buffer[2] = (divider >> 8) & 0x7f;
        buffer[3] = (divider >> 0) & 0xff;
    }

    fDivider = divider;

    fPort.Write(fAddress, buffer, sizeof(buffer));
}