NetBSD/sys/arch/evbarm/mini2440/audio_mini2440.c
nisimura 77b78cdcff Add FriendlyARM Mini2440 support.
- new code for Samsung S3C2440 SoC.
- update for other S3C2xx0.
This port was done by Paul Fleischer.
2012-01-30 03:28:33 +00:00

520 lines
13 KiB
C

/*-
* Copyright (c) 2012 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Paul Fleischer <paul@xpg.dk>
*
* 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/malloc.h>
#include <sys/fcntl.h>
#include <sys/audioio.h>
#include <sys/bus.h>
#include <dev/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;
bool sc_open;
};
int uda_ssio_open(void *, int);
void uda_ssio_close(void *);
int uda_ssio_set_params(void *, int, int, audio_params_t *, audio_params_t *,
stream_filter_list_t *, stream_filter_list_t *);
int uda_ssio_round_blocksize(void *, int, int, const audio_params_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_getprops(void *);
void uda_ssio_get_locks(void *, kmutex_t**, kmutex_t**);
struct audio_hw_if uda1341_hw_if = {
uda_ssio_open,
uda_ssio_close,
NULL,
uda1341_query_encodings,
uda_ssio_set_params,
uda_ssio_round_blocksize,
NULL, /* commit_settings*/
NULL,
NULL,
uda_ssio_start_output,
uda_ssio_start_input,
uda_ssio_halt_output,
uda_ssio_halt_input,
NULL,
uda_ssio_getdev,
NULL,
uda1341_set_port,
uda1341_get_port,
uda1341_query_devinfo,
uda_ssio_allocm,
uda_ssio_freem,
uda_ssio_round_buffersize,
NULL, /* mappage */
uda_ssio_getprops,
NULL,
NULL,
NULL,
uda_ssio_get_locks
};
static struct audio_device uda1341_device = {
"MINI2240-UDA1341",
"0.1",
"uda_ssio"
};
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;
sc->sc_open = false;
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)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
int retval;
DPRINTF(("%s\n", __func__));
if (sc->sc_open)
return EBUSY;
/* 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;
}
sc->sc_open = true;
return 0; /* SUCCESS */
}
void
uda_ssio_close(void *handle)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
DPRINTF(("%s\n", __func__));
uda1341_close(handle);
sc->sc_open = false;
}
int
uda_ssio_set_params(void *handle, int setmode, int usemode,
audio_params_t *play, audio_params_t *rec,
stream_filter_list_t *pfil, stream_filter_list_t *rfil)
{
struct uda1341_softc *uc = handle;
struct uda_softc *sc = uc->parent;
const struct audio_format *selected_format;
audio_params_t *params;
stream_filter_list_t *fil;
int retval;
DPRINTF(("%s: setmode: %d\n", __func__, setmode));
DPRINTF(("%s: usemode: %d\n", __func__, usemode));
if (setmode == 0)
setmode = usemode;
if (setmode & AUMODE_PLAY) {
params = play;
fil = pfil;
} else if (setmode == AUMODE_RECORD) {
params = rec;
fil = rfil;
} else {
return EINVAL;
}
DPRINTF(("%s: %dHz, encoding: %d, precision: %d, channels: %d\n",
__func__, params->sample_rate, params->encoding, play->precision,
params->channels));
if (params->sample_rate != 8000 &&
params->sample_rate != 11025 &&
params->sample_rate != 22050 &&
params->sample_rate != 32000 &&
params->sample_rate != 44100 &&
params->sample_rate != 48000) {
return EINVAL;
}
retval = auconv_set_converter(uda1341_formats, UDA1341_NFORMATS,
setmode, params, true, fil);
if (retval < 0) {
printf("Could not find valid format\n");
return EINVAL;
}
selected_format = &uda1341_formats[retval];
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, params->sample_rate);
s3c2440_i2s_set_sample_width(sc->sc_i2s_handle,
selected_format->precision);
/* It is vital that sc_system_clock is set PRIOR to calling
uda1341_set_params. */
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_params(handle, setmode, usemode,
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_round_blocksize(void *handle, int bs, int mode,
const audio_params_t *param)
{
int out_bs;
DPRINTF(("%s: %d\n", __func__, bs));
out_bs = (bs + 0x03) & ~0x03;
DPRINTF(("%s: out_bs: %d\n", __func__, out_bs));
return out_bs;
}
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_getprops(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
}