summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_hdaudio/hdaudio_si3055.c
blob: d47fbbd73320d3831ffac686769b52dc84315283 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
 * Purpose: Driver for Si3055 and compatible modems.
 */
/*
 *
 * This file is part of Open Sound System.
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This this source file is released under GPL v2 license (no other versions).
 * 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));
}