/*-
* Copyright (c) 2012 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Paul Fleischer <
[email protected]>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <sys/audioio.h>
#include <sys/bus.h>
#include <dev/audio/audio_if.h>
#include <dev/ic/uda1341var.h>
#include <arch/arm/s3c2xx0/s3c2440reg.h>
#include <arch/arm/s3c2xx0/s3c2440var.h>
#include <arch/arm/s3c2xx0/s3c2440_dma.h>
#include <arch/arm/s3c2xx0/s3c2440_i2s.h>
/*#define AUDIO_MINI2440_DEBUG*/
#ifdef AUDIO_MINI2440_DEBUG
#define DPRINTF(x) do {printf x; } while (/*CONSTCOND*/0)
#else
#define DPRINTF(s) do {} while (/*CONSTCOND*/0)
#endif
struct uda_softc {
device_t sc_dev;
kmutex_t sc_lock;
kmutex_t sc_intr_lock;
struct uda1341_softc sc_uda1341;
s3c2440_i2s_buf_t sc_play_buf;
s3c2440_i2s_buf_t sc_rec_buf;
void *sc_i2s_handle;
};
int uda_ssio_open(void *, int);
void uda_ssio_close(void *);
int uda_ssio_query_format(void *, audio_format_query_t *);
int uda_ssio_set_format(void *, int,
const audio_params_t *, const audio_params_t *,
audio_filter_reg_t *, audio_filter_reg_t *);
int uda_ssio_start_output(void *, void *, int, void (*)(void *),
void *);
int uda_ssio_start_input(void *, void *, int, void (*)(void *),
void *);
int uda_ssio_halt_output(void *);
int uda_ssio_halt_input(void *);
int uda_ssio_getdev(void *, struct audio_device *ret);
void* uda_ssio_allocm(void *, int, size_t);
void uda_ssio_freem(void *, void *, size_t);
size_t uda_ssio_round_buffersize(void *, int, size_t);
int uda_ssio_get_props(void *);
void uda_ssio_get_locks(void *, kmutex_t**, kmutex_t**);
struct audio_hw_if uda1341_hw_if = {
.open = uda_ssio_open,
.close = uda_ssio_close,
.query_format = uda_ssio_query_format,
.set_format = uda_ssio_set_format,
.start_output = uda_ssio_start_output,
.start_input = uda_ssio_start_input,
.halt_output = uda_ssio_halt_output,
.halt_input = uda_ssio_halt_input,
.getdev = uda_ssio_getdev,
.set_port = uda1341_set_port,
.get_port = uda1341_get_port,
.query_devinfo = uda1341_query_devinfo,
.allocm = uda_ssio_allocm,
.freem = uda_ssio_freem,
.round_buffersize = uda_ssio_round_buffersize,
.get_props = uda_ssio_get_props,
.get_locks = uda_ssio_get_locks
};
static struct audio_device uda1341_device = {
"MINI2240-UDA1341",
"0.1",
"uda_ssio"
};
static const struct audio_format uda_ssio_formats[] =
{
{
.mode = AUMODE_PLAY | AUMODE_RECORD,
.encoding = AUDIO_ENCODING_SLINEAR_LE,
.validbits = 16,
.precision = 16,
.channels = 2,
.channel_mask = AUFMT_STEREO,
.frequency_type = 6,
.frequency = { 8000, 11025, 22050, 32000, 44100, 48000 },
}
};
#define UDA_SSIO_NFORMATS __arraycount(uda_ssio_formats)
void uda_ssio_l3_write(void *,int mode, int value);
int uda_ssio_match(device_t, cfdata_t, void*);
void uda_ssio_attach(device_t, device_t, void*);
CFATTACH_DECL_NEW(udassio, sizeof(struct uda_softc),
uda_ssio_match, uda_ssio_attach, NULL, NULL);
int
uda_ssio_match(device_t parent, cfdata_t match, void *aux)
{
DPRINTF(("%s\n", __func__));
/* Not quite sure how we can detect the UDA1341 chip */
return 1;
}
void
uda_ssio_attach(device_t parent, device_t self, void *aux)
{
/* struct s3c2xx0_attach_args *sa = aux;*/
struct uda_softc *sc = device_private(self);
struct s3c2xx0_softc *s3sc = s3c2xx0_softc; /* Shortcut */
struct s3c2440_i2s_attach_args *aa = aux;
uint32_t reg;
sc->sc_dev = self;
sc->sc_play_buf = NULL;
sc->sc_i2s_handle = aa->i2sa_handle;
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);
s3c2440_i2s_set_intr_lock(aa->i2sa_handle, &sc->sc_intr_lock);
/* arch/arm/s3c2xx0/s3c2440.c initializes the I2S subsystem for us */
/* Setup GPIO pins to output for L3 communication.
GPB3 (L3DATA) will have to be switched to input when reading
from the L3 bus.
GPB2 - L3MODE
GPB3 - L3DATA
GPB4 - L3CLOCK
TODO: Make this configurable
*/
reg = bus_space_read_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBCON);
reg = GPIO_SET_FUNC(reg, 2, 1);
reg = GPIO_SET_FUNC(reg, 3, 1);
reg = GPIO_SET_FUNC(reg, 4, 1);
bus_space_write_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBCON, reg);
reg = bus_space_read_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBDAT);
reg = GPIO_SET_DATA(reg, 4, 1);
reg = GPIO_SET_DATA(reg, 3, 0);
reg = GPIO_SET_DATA(reg, 2, 1);
bus_space_write_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBDAT, reg);
printf("\n");
/* uda1341_attach resets the uda1341 sc, so it has to be called before
attributes are set on the sc.*/
uda1341_attach(&sc->sc_uda1341);
/* Configure the UDA1341 Codec */
sc->sc_uda1341.parent = sc;
sc->sc_uda1341.sc_l3_write = uda_ssio_l3_write;
sc->sc_uda1341.sc_bus_format = UDA1341_BUS_MSB;
/* Configure I2S controller */
s3c2440_i2s_set_bus_format(sc->sc_i2s_handle, S3C2440_I2S_BUS_MSB);
// Attach
audio_attach_mi(&uda1341_hw_if, &sc->sc_uda1341, self);
}
int
uda_ssio_open(void *handle, int flags)
{
int retval;
DPRINTF(("%s\n", __func__));
/* We only support write operations */
if (!(flags & FREAD) && !(flags & FWRITE))
return EINVAL;
/* We can't do much more at this point than to
ask the UDA1341 codec to initialize itself
(for an unknown system clock)
*/
retval = uda1341_open(handle, flags);
if (retval != 0) {
return retval;
}
return 0; /* SUCCESS */
}
void
uda_ssio_close(void *handle)
{
DPRINTF(("%s\n", __func__));
uda1341_close(handle);
}
int
uda_ssio_query_format(void *handle, audio_format_query_t *afp)
{
return audio_query_format(uda_ssio_formats, UDA_SSIO_NFORMATS, afp);
}
int
uda_ssio_set_format(void *handle, int setmode,
const audio_params_t *play, const audio_params_t *rec,
audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
int retval;
DPRINTF(("%s: setmode: %d\n", __func__, setmode));
/* *play and *rec are the identical because !AUDIO_PROP_INDEPENDENT. */
DPRINTF(("%s: %dHz, encoding: %d, precision: %d, channels: %d\n",
__func__, play->sample_rate, play->encoding, play->precision,
play->channels));
if (setmode == AUMODE_PLAY) {
s3c2440_i2s_set_direction(sc->sc_i2s_handle,
S3C2440_I2S_TRANSMIT);
} else {
s3c2440_i2s_set_direction(sc->sc_i2s_handle,
S3C2440_I2S_RECEIVE);
}
s3c2440_i2s_set_sample_rate(sc->sc_i2s_handle, play->sample_rate);
s3c2440_i2s_set_sample_width(sc->sc_i2s_handle, 16);
/* It is vital that sc_system_clock is set PRIOR to calling
uda1341_set_format. */
switch (s3c2440_i2s_get_master_clock(sc->sc_i2s_handle)) {
case 384:
uc->sc_system_clock = UDA1341_CLOCK_384;
break;
case 256:
uc->sc_system_clock = UDA1341_CLOCK_256;
break;
default:
return EINVAL;
}
retval = uda1341_set_format(handle, setmode, play, rec, pfil, rfil);
if (retval != 0) {
return retval;
}
/* Setup and enable I2S controller */
retval = s3c2440_i2s_commit(sc->sc_i2s_handle);
if (retval != 0) {
printf("Failed to setup I2S controller\n");
return retval;
}
return 0;
}
int
uda_ssio_start_output(void *handle, void *block, int bsize,
void (*intr)(void *), void *intrarg)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
return s3c2440_i2s_output(sc->sc_play_buf, block, bsize, intr, intrarg);
}
int
uda_ssio_start_input(void *handle, void *block, int bsize,
void (*intr)(void *), void *intrarg)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
return s3c2440_i2s_input(sc->sc_rec_buf, block, bsize, intr, intrarg);
}
int
uda_ssio_halt_output(void *handle)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
return s3c2440_i2s_halt_output(sc->sc_play_buf);
}
int
uda_ssio_halt_input(void *handle)
{
DPRINTF(("%s\n", __func__));
return 0;
}
int
uda_ssio_getdev(void *handle, struct audio_device *ret)
{
*ret = uda1341_device;
return 0;
}
void *
uda_ssio_allocm(void *handle, int direction, size_t size)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
void *retval = NULL;
DPRINTF(("%s\n", __func__));
if (direction == AUMODE_PLAY ) {
if (sc->sc_play_buf != NULL)
return NULL;
s3c2440_i2s_alloc(sc->sc_i2s_handle, direction, size, 0x00, &sc->sc_play_buf);
DPRINTF(("%s: addr of ring buffer: %p\n", __func__, sc->sc_play_buf->i2b_addr));
retval = sc->sc_play_buf->i2b_addr;
} else if (direction == AUMODE_RECORD) {
if (sc->sc_rec_buf != NULL)
return NULL;
s3c2440_i2s_alloc(sc->sc_i2s_handle, direction, size, 0x00, &sc->sc_rec_buf);
DPRINTF(("%s: addr of ring buffer: %p\n", __func__, sc->sc_rec_buf->i2b_addr));
retval = sc->sc_rec_buf->i2b_addr;
}
DPRINTF(("buffer: %p", retval));
return retval;
}
void
uda_ssio_freem(void *handle, void *ptr, size_t size)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
DPRINTF(("%s\n", __func__));
if (ptr == sc->sc_play_buf->i2b_addr)
s3c2440_i2s_free(sc->sc_play_buf);
else if (ptr == sc->sc_rec_buf->i2b_addr)
s3c2440_i2s_free(sc->sc_rec_buf);
}
size_t
uda_ssio_round_buffersize(void *handle, int direction, size_t bufsize)
{
DPRINTF(("%s: %d\n", __func__, (int)bufsize));
return bufsize;
}
int
uda_ssio_get_props(void *handle)
{
return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE;
}
void
uda_ssio_get_locks(void *handle, kmutex_t **intr, kmutex_t **thread)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
//struct uda_softc *sc = handle;
*intr = &sc->sc_intr_lock;
*thread = &sc->sc_lock;
}
void
uda_ssio_l3_write(void *cookie, int mode, int value)
{
struct s3c2xx0_softc *s3sc = s3c2xx0_softc; /* Shortcut */
uint32_t reg;
/* GPB2: L3MODE
GPB3: L2DATA
GPB4: L3CLOCK */
#define L3MODE 2
#define L3DATA 3
#define L3CLOCK 4
#define READ_GPIO() bus_space_read_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBDAT)
#define WRITE_GPIO(val) bus_space_write_4(s3sc->sc_iot, s3sc->sc_gpio_ioh, GPIO_PBDAT, val)
#define DELAY_TIME 1
reg = READ_GPIO();
reg = GPIO_SET_DATA(reg, L3CLOCK, 1);
reg = GPIO_SET_DATA(reg, L3MODE, mode);
reg = GPIO_SET_DATA(reg, L3DATA, 0);
WRITE_GPIO(reg);
if (mode == 1 ) {
reg = READ_GPIO();
reg = GPIO_SET_DATA(reg, L3MODE, 1);
WRITE_GPIO(reg);
}
DELAY(1); /* L3MODE setup time: min 190ns */
for(int i = 0; i<8; i++) {
char bval = (value >> i) & 0x1;
reg = READ_GPIO();
reg = GPIO_SET_DATA(reg, L3CLOCK, 0);
reg = GPIO_SET_DATA(reg, L3DATA, bval);
WRITE_GPIO(reg);
DELAY(DELAY_TIME);
reg = READ_GPIO();
reg = GPIO_SET_DATA(reg, L3CLOCK, 1);
reg = GPIO_SET_DATA(reg, L3DATA, bval);
WRITE_GPIO(reg);
DELAY(DELAY_TIME);
}
reg = READ_GPIO();
reg = GPIO_SET_DATA(reg, L3MODE, 1);
reg = GPIO_SET_DATA(reg, L3CLOCK, 1);
reg = GPIO_SET_DATA(reg, L3DATA, 0);
WRITE_GPIO(reg);
#undef L3MODE
#undef L3DATA
#undef L3CLOCK
#undef DELAY_TIME
#undef READ_GPIO
#undef WRITE_GPIO
}