/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Copyright (C) 2002,2003 Florian Schirmer <jolt@tuxbox.org>
 *
 * Copyright (C) 2007 NXP B.V.
 * All Rights Reserved.
 *
 * NXP based DVB adapter driver.
 * Based on the bt8xx driver by Florian Schirmer <jolt@tuxbox.org>
 *
*//*

Rev Date        Author        Comments
--------------------------------------------------------------------------------
001             Chris Steel   port from STB810
002 20060316    Chris Steel   add tuner for STB220
003 20060320    Chris Steel   fix section filtering
004 20060511    G.Lawrance    Imported emulation speed-ups from C.Steel
005 20060922    Chris Steel   update tuner support for STB225
006 20070116    Chris Steel   update for 2.6.19.1 kernel
007 20070119    Chris Steel   fix tuner allocation
008 20070126    Chris Steel   fix tuner i2c errors
009 20070130    Mike Neill    move to NXP prefix
010 20070131    Mike Neill    Change startup text
011 20070205    Chris Steel   use hotplug firmware download by default
012 23022007    Chris Steel   add ca
013 21032007    Chris Steel   align kernel config symbols
014 04042007    Chris Steel   swap tuners over for single tuner STB225
011 20070912    MikeWhittaker Re-enable TU1216 capability, selection with prjconfig
11.1.1 20071101 MikeWhittaker (gbr02691) Add calls to gate I2C bus off for 8271 tuner
11.1.2 20071129 MikeWhittaker (gbr02691) Remove/factor dependencies on 10021 frontend into CONFIG_10021
012 25012008    Chris Steel   swap tuners back for dual tuner STB225
11.1.5 15052008 Ch. Grosse    split in several files for future modularization
013 19023009    Chris Steel   add set_desc_type 
014 23023009    Chris Steel   add set_ts_type 
014 23023013    O. Courson    Remove network dependancy 
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include "phStbDemux_common.h"
#include "phStbDemux_dmx.h"
#include "phStbDemux_frontend.h"
#include "phStbDemux_ca.h"

static struct dvb_phStb_card phStb_demux[MAX_SUPPORTED_DEMUX];

static int dvb_phStb_get_stc(struct dmx_demux *demux, unsigned int num, u64 *stc, unsigned int *base)
{
   struct dvb_demux       *dvbdmx=(struct dvb_demux *) demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc = 0;

   dprintk("dvb_phStb: get_stc\n");

   rc = card->command->get_stc(card->hw, num, stc, base);

   return rc;
}

static int dvb_phStb_write_to_demux(struct dvb_demux *dvbdmx, const u8 *buf, size_t len)
{
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc = 0;

   dprintk("dvb_phStb: write to demux\n");

   if (card->pdma)
   {
      rc = card->command->dvr_load(card->hw, buf, len);
   }
   else
   {
      rc = -EINVAL;
   }

   return rc;
}

static int dvb_phStb_set_ts_filter(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux      *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   struct phStb_ts_filter filter;
   int rc = 0;

   dprintk("dvb_phStb: set_ts_filter\n");
   dprintk("pes_type = %d, buffer size %d\n", dvbdmxfeed->pes_type, dvbdmxfeed->buffer_size);

   filter.pid        = dvbdmxfeed->pid;
   filter.ts_type    = dvbdmxfeed->ts_type;
   filter.pes_type   = dvbdmxfeed->pes_type;
   filter.callback   = dvbdmxfeed->cb.ts;
   filter.feed       = &dvbdmxfeed->feed.ts;

   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_set_ts_filter failed to get mutex\n");
      return -ERESTARTSYS;
   }
   rc = card->command->set_ts_filter(card->hw, &filter, card->pdma);
   up(&card->lock);

   return rc;
}

static int dvb_phStb_del_ts_filter(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux       *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card  *card   = dvbdmx->priv;
   struct phStb_ts_filter  filter;
   int rc = 0;

   dprintk("dvb_phStb: del_ts_filter\n");

   filter.pid        = dvbdmxfeed->pid;
   filter.ts_type    = dvbdmxfeed->ts_type;
   filter.pes_type   = dvbdmxfeed->pes_type;
   filter.callback   = dvbdmxfeed->cb.ts;

   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_del_ts_filter failed to get mutex\n");
      return -ERESTARTSYS;
   }
   rc = card->command->del_ts_filter(card->hw, &filter, card->pdma);
   up(&card->lock);

   return rc;
}

static int dvb_phStb_set_section_filter(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux           *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card      *card   = dvbdmx->priv;
   struct phStb_section_filter filter;
   int rc = 0;

   dprintk("dvb_phStb: set_section_filter pid %d\n", dvbdmxfeed->pid);
   dprintk("buffer size %d\n", dvbdmxfeed->buffer_size);
   filter.pid        = dvbdmxfeed->pid;
   filter.callback   = dvbdmxfeed->cb.sec;
   filter.filter     = &dvbdmxfeed->filter->filter;

   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_set_section_filter failed to get mutex\n");
      return -ERESTARTSYS;
   }
   rc = card->command->set_section_filter(card->hw, &filter, card->pdma);
   up(&card->lock);
   return rc;
}

static int dvb_phStb_del_section_filter(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux           *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card      *card   = dvbdmx->priv;
   struct phStb_section_filter filter;
   int rc = 0;

   dprintk("dvb_phStb: del_section_filter\n");

   filter.pid        = dvbdmxfeed->pid;
   filter.callback   = dvbdmxfeed->cb.sec;
   filter.filter     = &dvbdmxfeed->filter->filter;

   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_del_section_filter failed to get mutex\n");
      return -ERESTARTSYS;
   }
   rc = card->command->del_section_filter(card->hw, &filter, card->pdma);
   up(&card->lock);

   return rc;
}

static int dvb_phStb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux      *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc;

   dprintk("dvb_phStb: start_feed\n");

   if (!dvbdmx->dmx.frontend)
   {
      return -EINVAL;
   }

   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_start_feed failed to get mutex\n");
      return -ERESTARTSYS;
   }
   card->nfeeds++;
   rc = card->nfeeds;

   if (card->pdma)
   {
      card->command->dvr_start(card->hw, card->nfeeds);
   }
   else
   {
      card->command->start(card->hw, card->nfeeds);
   }

   up(&card->lock);
   return rc;
}

static int dvb_phStb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
{
   struct dvb_demux       *dvbdmx = dvbdmxfeed->demux;
   struct dvb_phStb_card  *card   = dvbdmx->priv;

   dprintk("dvb_phStb: stop_feed\n");

   if (!dvbdmx->dmx.frontend)
   {
      return -EINVAL;
   }
   if (down_interruptible(&card->lock))
   {
      dprintk("dvb_phStb_stop_feed failed to get mutex\n");
      return -ERESTARTSYS;
   }
   card->nfeeds--;

   if (card->pdma)
   {
      card->command->dvr_stop(card->hw, card->nfeeds);
   }
   else
   {
      card->command->stop(card->hw, card->nfeeds);
   }

   up(&card->lock);
   return 0;
}

static int dvb_phStb_set_desc_type(struct dmx_demux *demux, unsigned int type)
{
   struct dvb_demux       *dvbdmx=(struct dvb_demux *) demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc = 0;

   dprintk("dvb_phStb: _set_desc_type\n");

   rc = card->command->set_desc_type(card->hw, type);

   return rc;
}

static void dvb_phStb_set_desc_key(struct dmx_demux *demux, int index, int parity, unsigned char *cw)
{
   struct dvb_demux       *dvbdmx=(struct dvb_demux *) demux;
   struct dvb_phStb_card *card = dvbdmx->priv;

   dprintk("dvb_phStb: set_desc_word\n");

   card->command->set_desc_word(card->hw, index, parity, cw);

   return;
}

static int dvb_phStb_set_desc_pid(struct dmx_demux *demux, int index, unsigned int pid)
{
   struct dvb_demux       *dvbdmx=(struct dvb_demux *) demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc = 0;

   dprintk("dvb_phStb: _set_desc_pid\n");

   rc = card->command->set_desc_pid(card->hw, index, pid);

   return rc;
}

static int dvb_phStb_set_ts_type(struct dmx_demux *demux, unsigned int type)
{
   struct dvb_demux       *dvbdmx=(struct dvb_demux *) demux;
   struct dvb_phStb_card *card = dvbdmx->priv;
   int rc = 0;

   dprintk("dvb_phStb: _set_ts_type\n");

   rc = card->command->set_ts_type(card->hw, type);

   return rc;
}

static int dvb_phStb_load_card(struct dvb_phStb_card *card, int instance_count, struct phStb_capabilities *caps)
{
   int result;

   dprintk("dvb_phStb: dvb_phStb_load_card\n");

   if ((result = dvb_register_adapter(&card->dvb_adapter, card->card_name, THIS_MODULE, NULL)) < 0)
   {
      dprintk("dvb_phStb: dvb_register_adapter failed (errno = %d)\n", result);
   }

   card->dvb_adapter.priv = card;

   if ((instance_count < MAX_SUPPORTED_TUNER) &&
       (dvb_phStb_i2c_client[instance_count] != NULL) &&
       (card->i2c_adapter = dvb_phStb_i2c_client[instance_count]->adapter) != NULL)
   {
      dprintk("dvb_phStb: got I2C adapter (%d)\n", instance_count);
   }

   card->pdma = 0;

   card->nfeeds = 0;
   card->ndescramblers = caps->numDescramblers;
   card->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;

   card->demux.priv               = card;
   card->demux.filternum          = caps->numPesFilters + caps->numSectionFilters;
   card->demux.feednum            = caps->numPesFilters + caps->numSectionFilters;
   card->demux.start_feed         = dvb_phStb_start_feed;
   card->demux.stop_feed          = dvb_phStb_stop_feed;
   card->demux.set_ts_filter      = dvb_phStb_set_ts_filter;
   card->demux.del_ts_filter      = dvb_phStb_del_ts_filter;
   card->demux.set_section_filter = dvb_phStb_set_section_filter;
   card->demux.del_section_filter = dvb_phStb_del_section_filter;
   card->demux.write_to_demux     = dvb_phStb_write_to_demux;
   card->demux.get_stc            = dvb_phStb_get_stc;
   card->demux.set_desc_type      = dvb_phStb_set_desc_type;
   card->demux.set_desc_key       = dvb_phStb_set_desc_key;
   card->demux.set_desc_pid       = dvb_phStb_set_desc_pid;
   card->demux.set_ts_type        = dvb_phStb_set_ts_type;

   if ((result = dvb_dmx_init(&card->demux)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   card->dmxdev.filternum    = caps->numPesFilters + caps->numSectionFilters;
   card->dmxdev.demux        = &card->demux.dmx;
   card->dmxdev.capabilities = 0;

   if ((result = dvb_dmxdev_init(&card->dmxdev, &card->dvb_adapter)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmxdev_init failed (errno = %d)\n", result);

      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   card->fe_hw.source = DMX_FRONTEND_0;

   if ((result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_hw)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      dvb_dmxdev_release(&card->dmxdev);
      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   if ((result = card->demux.dmx.connect_frontend(&card->demux.dmx, &card->fe_hw)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
      dvb_dmxdev_release(&card->dmxdev);
      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   if ((result = phStbDemux_ca_register(&card->dvb_adapter, &card->ca_dev, card)) < 0)
   {
      dprintk("dvb_pnx8550: phStb_ca_register failed (errno = %d)\n", result);
   }

#if defined(CONFIG_NET)
   dvb_net_init(&card->dvb_adapter, &card->dvbnet, &card->demux.dmx);
#endif

#if !(defined(CONFIG_IC_EMULATION) || defined(TMFL_IC_EMULATION))
   frontend_init(card, instance_count);
#else
   printk("IC_EMULATION : phStbDemux Skipping frontend_init\n");
#endif
   return 0;
}

static int dvb_phStb_load_dvr(struct dvb_phStb_card *card, struct phStb_capabilities *caps)
{
   int result;

   dprintk("dvb_phStb: dvb_phStb_load_dvr\n");

   if ((result = dvb_register_adapter(&card->dvb_adapter, card->card_name, THIS_MODULE, NULL)) < 0)
   {
       dprintk("dvb_phStb: dvb_register_adapter failed (errno = %d)\n", result);
       return result;
   }

   card->dvb_adapter.priv = card;
   card->pdma = 1;
   card->nfeeds = 0;
   card->ndescramblers = caps->numDescramblers;

   card->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING | DMX_MEMORY_BASED_FILTERING;

   card->demux.priv               = card;
   card->demux.filternum          = caps->numPesFilters + caps->numSectionFilters;
   card->demux.feednum            = caps->numPesFilters + caps->numSectionFilters;
   card->demux.start_feed         = dvb_phStb_start_feed;
   card->demux.stop_feed          = dvb_phStb_stop_feed;
   card->demux.set_ts_filter      = dvb_phStb_set_ts_filter;
   card->demux.del_ts_filter      = dvb_phStb_del_ts_filter;
   card->demux.set_section_filter = dvb_phStb_set_section_filter;
   card->demux.del_section_filter = dvb_phStb_del_section_filter;
   card->demux.write_to_demux     = dvb_phStb_write_to_demux;
   card->demux.set_desc_type      = dvb_phStb_set_desc_type;
   card->demux.set_desc_key       = dvb_phStb_set_desc_key;
   card->demux.set_desc_pid       = dvb_phStb_set_desc_pid;
   card->demux.set_ts_type        = dvb_phStb_set_ts_type;

   if ((result = dvb_dmx_init(&card->demux)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   card->dmxdev.filternum    = caps->numPesFilters + caps->numSectionFilters;
   card->dmxdev.demux        = &card->demux.dmx;
   card->dmxdev.capabilities = 0;

   if ((result = dvb_dmxdev_init(&card->dmxdev, &card->dvb_adapter)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmxdev_init failed (errno = %d)\n", result);

      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   card->fe_hw.source = DMX_MEMORY_FE;

   if ((result = card->demux.dmx.add_frontend(&card->demux.dmx, &card->fe_hw)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      dvb_dmxdev_release(&card->dmxdev);
      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   if ((result = card->demux.dmx.connect_frontend(&card->demux.dmx, &card->fe_hw)) < 0)
   {
      dprintk("dvb_phStb: dvb_dmx_init failed (errno = %d)\n", result);

      card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
      dvb_dmxdev_release(&card->dmxdev);
      dvb_dmx_release(&card->demux);
      dvb_unregister_adapter(&card->dvb_adapter);
      return result;
   }

   if ((result = phStbDemux_ca_register(&card->dvb_adapter, &card->ca_dev, card)) < 0)
   {
      dprintk("dvb_pnx8550: phStb_ca_register failed (errno = %d)\n", result);
   }

   return 0;
}

int dvb_phStb_register(int index, void *hw, struct phStb_demux_device *command, struct phStb_capabilities *caps)
{
   struct dvb_phStb_card *card = &phStb_demux[index];
   int ret;

   dprintk("dvb_phStb: registering card %d\n", index);

   memset(card, 0, sizeof(struct dvb_phStb_card));
   init_MUTEX(&card->lock);

   sprintf(card->card_name, "phStb%d", index);
   card->hw = hw;
   card->command = command;

   ret = dvb_phStb_load_card(card, index, caps);

   return ret;
}

int dvb_phStb_dvr_register(int index, void *hw, struct phStb_demux_device *command, struct phStb_capabilities *caps)
{
   struct dvb_phStb_card *card = &phStb_demux[index];
   int ret;

   dprintk("dvb_phStb: registering pdma %d\n", index);

   memset(card, 0, sizeof(struct dvb_phStb_card));
   init_MUTEX(&card->lock);

   sprintf(card->card_name, "phStbDvr%d", index);
   card->hw = hw;
   card->command = command;

   ret = dvb_phStb_load_dvr(card, caps);

   return ret;
}

int dvb_phStb_remove(int index)
{
   struct dvb_phStb_card *card = &phStb_demux[index];

   dprintk("dvb_phStb: unloading card %d\n", index);

   card->command->stop(card->hw, 0);
   phStbDemux_ca_unregister(card->ca_dev);
#if defined(CONFIG_NET)
   dvb_net_release(&card->dvbnet);
#endif
   card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
   dvb_dmxdev_release(&card->dmxdev);
   dvb_dmx_release(&card->demux);

   if (card->fe)
   {
      dvb_unregister_frontend(card->fe);
      dvb_unregister_adapter(&card->dvb_adapter);
   }

   return 0;
}

int dvb_phStb_dvr_remove(int index)
{
   struct dvb_phStb_card *card = &phStb_demux[index];

   dprintk("dvb_phStb: unloading dvr %d\n", index);

   card->command->dvr_stop(card->hw, 0);
   phStbDemux_ca_unregister(card->ca_dev);
   card->demux.dmx.remove_frontend(&card->demux.dmx, &card->fe_hw);
   dvb_dmxdev_release(&card->dmxdev);
   dvb_dmx_release(&card->demux);

   if (card->fe)
   {
      dvb_unregister_frontend(card->fe);
      dvb_unregister_adapter(&card->dvb_adapter);
   }

   return 0;
}

EXPORT_SYMBOL(dvb_phStb_register);
EXPORT_SYMBOL(dvb_phStb_dvr_register);
EXPORT_SYMBOL(dvb_phStb_remove);
EXPORT_SYMBOL(dvb_phStb_dvr_remove);

