/*
 * Purpose: Driver for Si3055 and compatible modems.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */



/*
 * Documentation Note
 * 
 * There is no publicly available documentation for Si3055. However,
 * there is a very similar modem (Si3038) for which a datasheet is
 * publicly available:
 * 
 * https://www.silabs.com/Support%20Documents/TechnicalDocs/si3038.pdf
 * 
 * This driver was written by reading the ALSA code, looking for a
 * similar modem (Si3038), and figuring out the corresponding Si3055
 * register IDs for Si3038 registers, again by reading the ALSA code,
 * and by checking the default after-reset values.
 * 
 */

#include "oss_hdaudio_cfg.h"
#include "hdaudio.h"
#include "hdaudio_codec.h"
#include "hdaudio_dedicated.h"
#include "hdaudio_mixers.h"

/*
 * Si3055 register IDs.
 */
#define SI3055_EXT_MODEM_STATUS 2
#define SI3055_LINE_RATE  3
#define SI3055_HDA_STREAMS      4
#define SI3055_GPIO_CONFIG      5
#define SI3055_GPIO_PIN_STATUS  10
#define SI3055_LINE_CONFIG      13


/* Corresponding registers in Si3038 (for reference):
 * 
 * SI3055_EXT_MODEM_STATUS      3Eh (Extended Modem Status & Control)
 * SI3055_LINE_RATE             40h (Line 1 DAC/ADC Rate)
 * SI3055_GPIO_PIN_STATUS       54h (GPIO Pin Status)
 * SI3055_LINE_CONFIG           56h (Line Side Configuration 1)
 */

/*
 * The SI3055_HDA_STREAMS register has no corresponding in Si3038.
 * It contains the playback and recording stream descriptors in the
 * following format:
 * 
 *    ((playback_stream_num << 4) << 8) | (recording_stream_num << 4)
 */

/* Si3055 verbs. */
#define SI3055_REG_GET_VERB     0x900
#define SI3055_REG_SET_VERB     0x100

/* Convenience macros for reading from and writing to Si3055 registers. */
#define SI3055_REG_GET(mixer, cad, reg, a, b) corb_read(mixer, cad, reg, 0, SI3055_REG_GET_VERB, 0, a, b)
#define SI3055_REG_SET(mixer, cad, reg, val)  corb_write(mixer, cad, reg, 0, SI3055_REG_SET_VERB, val)


void hdaudio_si3055_set_rate(hdaudio_mixer_t *mixer, int cad, int rate)
{
  SI3055_REG_SET(mixer, cad, SI3055_LINE_RATE, rate);
}

int hdaudio_si3055_set_offhook(hdaudio_mixer_t *mixer, int cad, int offhook)
{
  unsigned int a, b;
  SI3055_REG_GET(mixer, cad, SI3055_GPIO_PIN_STATUS, &a, &b);

  if (offhook)
    {
      a |= 0x1;    /* Set Off-Hook bit */
    }
  else
    {
      a &= ~0x1;   /* Unset Off-Hook bit */
    }

  SI3055_REG_SET(mixer, cad, SI3055_GPIO_PIN_STATUS, a);
  SI3055_REG_GET(mixer, cad, SI3055_GPIO_PIN_STATUS, &a, &b);
  return ((a & 0x1) == 0x1);
}

void hdaudio_si3055_endpoint_init(hdaudio_mixer_t *mixer, int cad)
{
  codec_t *codec = mixer->codecs[cad];  /* Modem codec */
  widget_t *widget;                     /* MFG widget */
  hdaudio_endpointinfo_t *endpoint;
  unsigned int a, b;      /* Used for reading data. */
  int tmout;              /* Timeout counter. */

  /* Output and input stream numbers. */
  int playback_stream_num, recording_stream_num;

  
  DDB(cmn_err(CE_CONT, "hdaudio_si3055_endpoint_init got called.\n"));

  /* Reset the modem codec. */
  corb_write(mixer, cad, 0x00, 0, SET_CODEC_RESET, 0);
  corb_write(mixer, cad, codec->afg, 0, SET_CONVERTER, IDDLE_STREAM << 4);
  corb_write(mixer, cad, codec->afg, 0, SET_CONVERTER_FORMAT, 0);
  
  /* Set 9600Hz as the initial line sampling rate.
   * It can be changed later when desired.
   */
  SI3055_REG_SET(mixer, cad, SI3055_LINE_RATE, 9600);
  
  /* Assign the "unused" value to the playback and recording
   * stream descriptors (ref. HDAudio_03.pdf, page 40).
   */
  SI3055_REG_SET(mixer, cad, SI3055_HDA_STREAMS, 0x0000);
  
  /* Write 0x0000 to the Extended Modem Status & Control register
   * to power up the modem (ref. si3038.pdf, page 22).
   */
  SI3055_REG_SET(mixer, cad, SI3055_EXT_MODEM_STATUS, 0x0000);
  
  /* Wait for the modem to complete power up. The lower 8 bits
   * indicate that it is ready (ref. si3038.pdf, page 22).
   */
  tmout = 10;
  do
    {
      SI3055_REG_GET(mixer, cad, SI3055_EXT_MODEM_STATUS, &a, &b);
      DDB(cmn_err(CE_CONT, "si3055: ext modem status: %04x.\n", a));
      oss_udelay(1000);
    }
    while(((a & 0xf) == 0) && --tmout);
  
  if ((a & 0xf) == 0)
    {
      cmn_err(CE_WARN, "si3055: power up timeout (status: %04x).\n", a);
    }
  
  /* This register contains 0x1fff after reset. We need to set it
   * to zero to get the modem working. No corresponding register
   * could be found in the Si3038 datasheet.
   */
  SI3055_REG_SET(mixer, cad, SI3055_GPIO_CONFIG, 0x0000);

  /* Program line interface parameters. The register value after
   * a reset is 0xF010. Set it to 0x0010 to unmute the analog
   * receive and transmit paths.
   */
  SI3055_REG_SET(mixer, cad, SI3055_LINE_CONFIG, 0x0010);
      
  /* Setup the widget info. */
  widget = &codec->widgets[codec->afg];
  widget->endpoint = &codec->inendpoints[0];
  widget->sizes = 0x20000; /* 16 bits */
  strcpy(widget->name, "modem");

  /* Setup the output endpoint. */
  codec->num_outendpoints = 1;
  endpoint = &codec->outendpoints[0];
  endpoint->ix = 0;
  endpoint->iddle_stream = 0;
  endpoint->cad = cad;
  endpoint->base_wid = codec->afg;
  endpoint->recsrc_wid = endpoint->volume_wid = -1;
  endpoint->nrates = 3;
  endpoint->rates[0] = 8000;
  endpoint->rates[1] = 9600;
  endpoint->rates[2] = 16000;
  endpoint->name = widget->name;
  endpoint->channels = 1;
  endpoint->is_digital = 0;
  endpoint->is_modem = 1;
  endpoint->sizemask = widget->sizes;
  endpoint->fmt_mask = AFMT_S16_LE;
  endpoint->afg = codec->afg;
  endpoint->min_rate = 8000;
  endpoint->max_rate = 16000;
  
  /* Setup the input endpoint. */
  codec->num_inendpoints = 1;
  memcpy(&codec->inendpoints[0], endpoint, sizeof(*endpoint));

  /* Choose stream numbers for output and input streams. */
  playback_stream_num = ++mixer->num_outendpoints;
  endpoint->stream_number = endpoint->default_stream_number = playback_stream_num;

  endpoint = &codec->inendpoints[0];
  recording_stream_num = ++mixer->num_inendpoints;
  endpoint->stream_number = endpoint->default_stream_number = recording_stream_num;

  /* Setup the stream numbers. */
  SI3055_REG_SET(mixer, cad, SI3055_HDA_STREAMS, ((playback_stream_num  << 4) << 8) |
                                                  (recording_stream_num << 4));
}

