blob: 20026b6c771a364d173621e0f976263c957d7894 [file] [log] [blame]
/*
* Copyright (C) 2012 Samsung Electronics
* R. Chandrasekar <rcsekar@samsung.com>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <asm/arch/pinmux.h>
#include <malloc.h>
#include <common.h>
#include <asm/io.h>
#include <libfdt.h>
#include <fdtdec.h>
#include <i2c.h>
#include <sound.h>
#include "i2s.h"
#include "wm8994.h"
#include "max98095.h"
#include "max98088.h"
/* defines */
#define SOUND_BITS_IN_BYTE 8
/* Globals */
struct i2stx_info g_i2stx_pri;
struct sound_codec_info g_codec_info;
/*
* get_sound_fdt_values gets fdt values for i2s parameters
*
* @param i2stx_info i2s transmitter transfer param structure
* @param blob FDT blob
* @return int value, 0 for success
*/
static int get_sound_i2s_fdt_values(struct i2stx_info *i2s, const void *blob)
{
int node;
int error = 0;
int base;
/* Get the node from FDT for sound */
node = fdtdec_next_compatible(blob, 0,
COMPAT_SAMSUNG_EXYNOS_SOUND);
if (node <= 0) {
debug("EXYNOS_SOUND: No node for sound in device tree\n");
return -1;
}
/*
* Get the pre-defined sound specific values from FDT.
* All of these are expected to be correct otherwise
* wrong register values in i2s setup parameters
* may result in no sound play.
*/
base = fdtdec_get_addr(blob, node, "reg");
if (base == FDT_ADDR_T_NONE) {
debug("%s: Missing i2s base\n", __func__);
return -1;
}
i2s->base_address = base;
i2s->audio_pll_clk = fdtdec_get_int(blob,
node, "samsung,i2s-epll-clock-frequency", -1);
error |= i2s->audio_pll_clk;
debug("audio_pll_clk = %d\n", i2s->audio_pll_clk);
i2s->samplingrate = fdtdec_get_int(blob,
node, "samsung,i2s-sampling-rate", -1);
error |= i2s->samplingrate;
debug("samplingrate = %d\n", i2s->samplingrate);
i2s->bitspersample = fdtdec_get_int(blob,
node, "samsung,i2s-bits-per-sample", -1);
error |= i2s->bitspersample;
debug("bitspersample = %d\n", i2s->bitspersample);
i2s->channels = fdtdec_get_int(blob,
node, "samsung,i2s-channels", -1);
error |= i2s->channels;
debug("channels = %d\n", i2s->channels);
i2s->rfs = fdtdec_get_int(blob,
node, "samsung,i2s-lr-clk-framesize", -1);
error |= i2s->rfs;
debug("rfs = %d\n", i2s->rfs);
i2s->bfs = fdtdec_get_int(blob,
node, "samsung,i2s-bit-clk-framesize", -1);
error |= i2s->bfs;
debug("bfs = %d\n", i2s->bfs);
if (error == -1) {
debug("fail to get sound i2s node properties\n");
return -1;
}
return 0;
}
/*
* Gets fdt values for wm8994 config parameters
*
* @param pcodec_info codec information structure
* @param blob FDT blob
* @return int value, 0 for success
*/
static int get_sound_fdt_values(struct sound_codec_info *pcodec_info,
const void *blob, enum fdt_compat_id compat_id)
{
enum fdt_compat_id compat;
int node;
int error = 0;
int parent;
/* Get the node from FDT for codec */
node = fdtdec_next_compatible(blob, 0, compat_id);
if (node <= 0) {
debug("EXYNOS_SOUND: No node for codec in device tree\n");
debug("node = %d\n", node);
return -1;
}
parent = fdt_parent_offset(blob, node);
if (parent < 0) {
debug("%s: Cannot find node parent\n", __func__);
return -1;
}
compat = fdtdec_lookup(blob, parent);
switch (compat) {
case COMPAT_SAMSUNG_EXYNOS_SPI:
debug("%s: Support not added for SPI interface\n", __func__);
return -1;
break;
case COMPAT_SAMSUNG_S3C2440_I2C:
pcodec_info->i2c_bus = i2c_get_bus_num_fdt(blob, parent);
error |= pcodec_info->i2c_bus;
debug("i2c bus = %d\n", pcodec_info->i2c_bus);
pcodec_info->i2c_dev_addr = fdtdec_get_int(blob, node, "reg", 0);
error |= pcodec_info->i2c_dev_addr;
debug("i2c dev addr = %d\n", pcodec_info->i2c_dev_addr);
break;
default:
debug("%s: Unknown compat id %d\n", __func__, compat);
return -1;
}
if (error == -1) {
debug("fail to get codec node properties\n");
return -1;
}
return 0;
}
/*
* Gets fdt values for codec config parameters
*
* @param pcodec_info codec information structure
* @param blob FDT blob
* @return int value, 0 for success
*/
static int get_sound_codec_fdt_values(struct sound_codec_info *pcodec_info,
const void *blob)
{
int node;
int error = 0;
const char *codectype;
/* Get the node from FDT for sound */
node = fdtdec_next_compatible(blob, 0, COMPAT_SAMSUNG_EXYNOS_SOUND);
if (node <= 0) {
debug("EXYNOS_SOUND: No node for sound in device tree\n");
debug("node = %d\n", node);
return -1;
}
/*
* Get the pre-defined sound codec specific values from FDT.
* All of these are expected to be correct otherwise sound
* can not be played
*/
codectype = fdt_getprop(blob, node, "samsung,codec-type", NULL);
debug("device = %s\n", codectype);
if (!strcmp(codectype, "wm8994")) {
pcodec_info->codec_type = CODEC_WM_8994;
error = get_sound_fdt_values(pcodec_info, blob,
COMPAT_WOLFSON_WM8994_CODEC);
} else if (!strcmp(codectype, "max98095")) {
pcodec_info->codec_type = CODEC_MAX_98095;
error = get_sound_fdt_values(pcodec_info, blob,
COMPAT_MAXIM_98095_CODEC);
} else if (!strcmp(codectype, "max98088")) {
pcodec_info->codec_type = CODEC_MAX_98088;
error = get_sound_fdt_values(pcodec_info, blob,
COMPAT_MAXIM_98088_CODEC);
} else
error = -1;
if (error == -1) {
debug("fail to get sound codec node properties\n");
return -1;
}
return 0;
}
int sound_init(const void *blob)
{
int ret;
struct i2stx_info *pi2s_tx = &g_i2stx_pri;
struct sound_codec_info *pcodec_info = &g_codec_info;
/* Get the I2S Values */
if (get_sound_i2s_fdt_values(pi2s_tx, blob) < 0)
return -1;
/* Get the codec Values */
if (get_sound_codec_fdt_values(pcodec_info, blob) < 0)
return -1;
ret = i2s_tx_init(pi2s_tx);
if (ret) {
debug("%s: Failed to init i2c transmit: ret=%d\n", __func__,
ret);
return ret;
}
/* Check the codec type and initialise the same */
if (pcodec_info->codec_type == CODEC_WM_8994) {
ret = wm8994_init(pcodec_info, WM8994_AIF2,
pi2s_tx->samplingrate,
(pi2s_tx->samplingrate * (pi2s_tx->rfs)),
pi2s_tx->bitspersample, pi2s_tx->channels);
} else if (pcodec_info->codec_type == CODEC_MAX_98095) {
#if defined CONFIG_SOUND_MAX98095
ret = max98095_init(pcodec_info, pi2s_tx->samplingrate,
(pi2s_tx->samplingrate * (pi2s_tx->rfs)),
pi2s_tx->bitspersample);
#else
ret = -1;
debug("%s: max98095 codec support not built in.\n", __func__);
#endif
} else if (pcodec_info->codec_type == CODEC_MAX_98088) {
#if defined CONFIG_SOUND_MAX98088
ret = max98088_init(pcodec_info, pi2s_tx->samplingrate,
(pi2s_tx->samplingrate * (pi2s_tx->rfs)),
pi2s_tx->bitspersample);
#else
ret = -1;
debug("%s: max98088 codec support not built in.\n", __func__);
#endif
} else {
debug("%s: Unknown code type %d\n", __func__,
pcodec_info->codec_type);
return -1;
}
if (ret) {
debug("%s: Codec init failed\n", __func__);
return -1;
}
return ret;
}
/*
* Generates square wave sound data for 1 second
*
* @param data data buffer pointer
* @param size size of the buffer
* @param freq frequency of the wave
*/
static void sound_prepare_buffer(unsigned short *data, int size, uint32_t freq)
{
const int sample = 48000;
const unsigned short amplitude = 16000; /* between 1 and 32767 */
const int period = freq ? sample / freq : 0;
const int half = period / 2;
assert(freq);
/* Make sure we don't overflow our buffer */
if (size % 2)
size--;
while (size) {
int i;
for (i = 0; size && i < half; i++) {
size -= 2;
*data++ = amplitude;
*data++ = amplitude;
}
for (i = 0; size && i < period - half; i++) {
size -= 2;
*data++ = -amplitude;
*data++ = -amplitude;
}
}
}
int sound_play(uint32_t msec, uint32_t frequency)
{
unsigned int *data;
unsigned long data_size;
unsigned int ret = 0;
/* Sine wave Buffer length computation */
data_size = g_i2stx_pri.samplingrate * g_i2stx_pri.channels;
data_size *= (g_i2stx_pri.bitspersample / SOUND_BITS_IN_BYTE);
data = malloc(data_size);
if (data == NULL ) {
debug("%s: malloc failed\n", __func__);
return -1;
}
sound_prepare_buffer((unsigned short *)data,
data_size / sizeof(unsigned short), frequency);
while (msec >= 1000) {
ret = i2s_transfer_tx_data(&g_i2stx_pri, data,
(data_size / sizeof(int)));
msec -= 1000;
}
if (msec) {
unsigned long size =
(data_size * msec) / (sizeof(int) * 1000);
ret = i2s_transfer_tx_data(&g_i2stx_pri, data, size);
}
free(data);
return ret;
}