GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)

This commit is contained in:
lai
2026-09-06 03:52:57 +08:00
commit b1928b41c0
21813 changed files with 4413081 additions and 0 deletions
@@ -0,0 +1,133 @@
menu "Sound support"
config SOUND
bool "Enable sound support"
help
Support making sounds through an audio codec. This is normally a
beep at a chosen frequency for a selected length of time. However
the drivers support playing arbitrary sound samples using a
PCM interface.
Note: At present the sound setup is somewhat tangled up in that the
audio codecs are called from the sound-i2s code. This could be
converted to driver model.
config I2S
bool "Enable I2S support"
depends on SOUND
help
I2S is a serial bus often used to transmit audio data from the
SoC to the audio codec. This option enables sound support using
I2S. It calls either of the two supported codecs (no use is made
of driver model at present).
config I2S_ROCKCHIP
bool "Enable I2S support for Rockchip SoCs"
depends on I2S
help
Rockchip SoCs support an I2S interface for sending audio data to an
audio codec. This option enables support for this, using one of the
available audio codec drivers. This driver does not make use of
DMA, but writes each word directly to the hardware.
config I2S_SAMSUNG
bool "Enable I2C support for Samsung SoCs"
depends on I2S
help
Samsung Exynos SoCs support an I2S interface for sending audio
data to an audio codec. This option enables support for this,
using one of the available audio codec drivers. Enabling this
option provides an implementation for sound_init() and
sound_play().
config SOUND_I8254
bool "Intel i8254 timer / beeper"
depends on SOUND
help
This enables support for a beeper that uses the i8254 timer chip.
This can emit beeps at a fixed frequency. It is possible to control
the length of the beeps, by turning a beep on, waiting for a period
of time, then turning it off.
This is quite an old feature, called PIT (Programmable Interval
Timer), but is nonetheless still available on modern x86 machines.
config SOUND_INTEL_HDA
bool "Intel HDA audio codec"
depends on SOUND
help
Most Intel chips have an HDA (High-definition audio) codec which can
be used by U-Boot to play simple beeps. This is also sometimes called
Azalia which was the development code-name. It requires setup
information in the device tree (see intel-hda.txt).
config SOUND_IVYBRIDGE
bool "Intel Ivybridge sound support"
depends on SOUND
select SOUND_INTEL_HDA
help
Enable sound output on supported Intel Ivybridge-based boards. This
driver uses Intel's High-definition Audio (HDA) architecture,
sometimes called Azalia. The audio codec is detected using a
semi-automatic mechanism.
config I2S_TEGRA
bool "Enable I2S support for Nvidia Tegra SoCs"
depends on I2S
select TEGRA124_DMA
help
Nvidia Tegra SoCs support several I2S interfaces for sending audio
data to an audio codec. This option enables support for this,
using one of the available audio codec drivers.
config SOUND_MAX98088
bool "Support Maxim max98088 audio codec"
depends on I2S
help
Enable the max98088 audio codec. This is connected via I2S for
audio data and I2C for codec control. At present it only works
with the Samsung I2S driver.
config SOUND_MAX98090
bool "Support Maxim max98090 audio codec"
depends on I2S
help
Enable the max98090 audio codec. This is connected via I2S for
audio data and I2C for codec control. At present it only works
with the Samsung I2S driver.
config SOUND_MAX98095
bool "Support Maxim max98095 audio codec"
depends on I2S
help
Enable the max98095 audio codec. This is connected via I2S for
audio data and I2C for codec control. At present it only works
with the Samsung I2S driver.
config SOUND_RT5677
bool "Support Realtek RT5677 audio codec"
depends on SOUND
help
Enable the Realtek RT5677 audio codec. This is an I2S device used on
some Chromebooks from around 2015 ('auron'). It is configured using
an I2C interface and supports multiple sound inputs and outputs,
including digital microphones.
config SOUND_SANDBOX
bool "Support sandbox emulated audio codec"
depends on SANDBOX && SOUND
help
U-Boot sandbox can emulate a sound device using SDL, playing the
sound on the host machine. This option implements the sound_init()
and sound_play() functions for sandbox. Note that you must install
the SDL libraries for this to work.
config SOUND_WM8994
bool "Support Wolfson Micro wm8994 audio codec"
depends on I2S_SAMSUNG
help
Enable the wm8994 audio codec. This is connected via I2S for
audio data and I2C for codec control. At present it only works
with the Samsung I2S driver.
endmenu
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2012 Samsung Electronics
# R. Chandrasekar <rcsekar@samsung.com>
obj-$(CONFIG_SOUND) += sound.o
obj-$(CONFIG_SOUND) += codec-uclass.o
obj-$(CONFIG_SOUND) += i2s-uclass.o
obj-$(CONFIG_SOUND) += sound-uclass.o
obj-$(CONFIG_I2S_SAMSUNG) += samsung-i2s.o
obj-$(CONFIG_SOUND_SANDBOX) += sandbox.o
obj-$(CONFIG_I2S_ROCKCHIP) += rockchip_i2s.o rockchip_sound.o
obj-$(CONFIG_I2S_SAMSUNG) += samsung_sound.o
obj-$(CONFIG_I2S_TEGRA) += tegra_ahub.o tegra_i2s.o tegra_sound.o
obj-$(CONFIG_SOUND_WM8994) += wm8994.o
obj-$(CONFIG_SOUND_MAX98088) += max98088.o maxim_codec.o
obj-$(CONFIG_SOUND_MAX98090) += max98090.o maxim_codec.o
obj-$(CONFIG_SOUND_MAX98095) += max98095.o maxim_codec.o
obj-$(CONFIG_SOUND_INTEL_HDA) += hda_codec.o
obj-$(CONFIG_SOUND_I8254) += i8254_beep.o
obj-$(CONFIG_SOUND_RT5677) += rt5677.o
obj-$(CONFIG_INTEL_BROADWELL) += broadwell_i2s.o broadwell_sound.o
obj-$(CONFIG_SOUND_IVYBRIDGE) += ivybridge_sound.o
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Intel Broadwell I2S driver
*
* Copyright 2019 Google LLC
*
* Modified from dc i2s/broadwell/broadwell.c
*/
#define LOG_CATEGORY UCLASS_I2S
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <time.h>
#include <asm/io.h>
#include "broadwell_i2s.h"
enum {
BDW_SHIM_START_ADDRESS = 0xfb000,
BDW_SSP0_START_ADDRESS = 0xfc000,
BDW_SSP1_START_ADDRESS = 0xfd000,
};
struct broadwell_i2s_priv {
enum frame_sync_rel_timing_t rel_timing;
enum frame_sync_pol_t sfrm_polarity;
enum end_transfer_state_t end_transfer_state;
enum clock_mode_t sclk_mode;
uint sclk_dummy_stop; /* 0-31 */
uint sclk_frame_width; /* 1-38 */
struct i2s_shim_regs *shim;
struct broadwell_i2s_regs *regs;
};
static void init_shim_csr(struct broadwell_i2s_priv *priv)
{
/*
* Select SSP clock
* Turn off low power clock
* Set PIO mode
* Stall DSP core
*/
clrsetbits_le32(&priv->shim->csr,
SHIM_CS_S0IOCS | SHIM_CS_LPCS | SHIM_CS_DCS_MASK,
SHIM_CS_S1IOCS | SHIM_CS_SBCS_SSP1_24MHZ |
SHIM_CS_SBCS_SSP0_24MHZ | SHIM_CS_SDPM_PIO_SSP1 |
SHIM_CS_SDPM_PIO_SSP0 | SHIM_CS_STALL |
SHIM_CS_DCS_DSP32_AF32);
}
static void init_shim_clkctl(struct i2s_uc_priv *uc_priv,
struct broadwell_i2s_priv *priv)
{
u32 clkctl = readl(&priv->shim->clkctl);
/* Set 24Mhz mclk, prevent local clock gating, enable SSP0 clock */
clkctl &= SHIM_CLKCTL_RESERVED;
clkctl |= SHIM_CLKCTL_MCLK_24MHZ | SHIM_CLKCTL_DCPLCG;
/* Enable requested SSP interface */
if (uc_priv->id)
clkctl |= SHIM_CLKCTL_SCOE_SSP1 | SHIM_CLKCTL_SFLCGB_SSP1_CGD;
else
clkctl |= SHIM_CLKCTL_SCOE_SSP0 | SHIM_CLKCTL_SFLCGB_SSP0_CGD;
writel(clkctl, &priv->shim->clkctl);
}
static void init_sscr0(struct i2s_uc_priv *uc_priv,
struct broadwell_i2s_priv *priv)
{
u32 sscr0;
uint scale;
/* Set data size based on BPS */
if (uc_priv->bitspersample > 16)
sscr0 = (uc_priv->bitspersample - 16 - 1) << SSP_SSC0_DSS_SHIFT
| SSP_SSC0_EDSS;
else
sscr0 = (uc_priv->bitspersample - 1) << SSP_SSC0_DSS_SHIFT;
/* Set network mode, Stereo PSP frame format */
sscr0 |= SSP_SSC0_MODE_NETWORK |
SSP_SSC0_FRDC_STEREO |
SSP_SSC0_FRF_PSP |
SSP_SSC0_TIM |
SSP_SSC0_RIM |
SSP_SSC0_ECS_PCH |
SSP_SSC0_NCS_PCH |
SSP_SSC0_ACS_PCH;
/* Scale 24MHz MCLK */
scale = uc_priv->audio_pll_clk / uc_priv->samplingrate / uc_priv->bfs;
sscr0 |= scale << SSP_SSC0_SCR_SHIFT;
writel(sscr0, &priv->regs->sscr0);
}
static void init_sscr1(struct broadwell_i2s_priv *priv)
{
u32 sscr1 = readl(&priv->regs->sscr1);
sscr1 &= SSP_SSC1_RESERVED;
/* Set as I2S master */
sscr1 |= SSP_SSC1_SCLKDIR_MASTER | SSP_SSC1_SCLKDIR_MASTER;
/* Enable TXD tristate behavior for PCH */
sscr1 |= SSP_SSC1_TTELP | SSP_SSC1_TTE;
/* Disable DMA Tx/Rx service request */
sscr1 |= SSP_SSC1_TSRE | SSP_SSC1_RSRE;
/* Clock on during transfer */
sscr1 |= SSP_SSC1_SCFR;
/* Set FIFO thresholds */
sscr1 |= SSP_FIFO_SIZE << SSP_SSC1_RFT_SHIFT;
sscr1 |= SSP_FIFO_SIZE << SSP_SSC1_TFT_SHIFT;
/* Disable interrupts */
sscr1 &= ~(SSP_SSC1_EBCEI | SSP_SSC1_TINTE | SSP_SSC1_PINTE);
sscr1 &= ~(SSP_SSC1_LBM | SSP_SSC1_RWOT);
writel(sscr1, &priv->regs->sscr1);
}
static void init_sspsp(struct broadwell_i2s_priv *priv)
{
u32 sspsp = readl(&priv->regs->sspsp);
sspsp &= SSP_PSP_RESERVED;
sspsp |= priv->sclk_mode << SSP_PSP_SCMODE_SHIFT;
sspsp |= (priv->sclk_dummy_stop << SSP_PSP_DMYSTOP_SHIFT) &
SSP_PSP_DMYSTOP_MASK;
sspsp |= (priv->sclk_dummy_stop >> 2 << SSP_PSP_EDYMSTOP_SHIFT) &
SSP_PSP_EDMYSTOP_MASK;
sspsp |= priv->sclk_frame_width << SSP_PSP_SFRMWDTH_SHIFT;
/* Frame Sync Relative Timing */
if (priv->rel_timing == NEXT_FRMS_AFTER_END_OF_T4)
sspsp |= SSP_PSP_FSRT;
else
sspsp &= ~SSP_PSP_FSRT;
/* Serial Frame Polarity */
if (priv->sfrm_polarity == SSP_FRMS_ACTIVE_HIGH)
sspsp |= SSP_PSP_SFRMP;
else
sspsp &= ~SSP_PSP_SFRMP;
/* End Data Transfer State */
if (priv->end_transfer_state == SSP_END_TRANSFER_STATE_LOW)
sspsp &= ~SSP_PSP_ETDS;
else
sspsp |= SSP_PSP_ETDS;
writel(sspsp, &priv->regs->sspsp);
}
static void init_ssp_time_slot(struct broadwell_i2s_priv *priv)
{
writel(3, &priv->regs->sstsa);
writel(3, &priv->regs->ssrsa);
}
static int bdw_i2s_init(struct udevice *dev)
{
struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct broadwell_i2s_priv *priv = dev_get_priv(dev);
init_shim_csr(priv);
init_shim_clkctl(uc_priv, priv);
init_sscr0(uc_priv, priv);
init_sscr1(priv);
init_sspsp(priv);
init_ssp_time_slot(priv);
return 0;
}
static void bdw_i2s_enable(struct broadwell_i2s_priv *priv)
{
setbits_le32(&priv->regs->sscr0, SSP_SSC0_SSE);
setbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN);
}
static void bdw_i2s_disable(struct broadwell_i2s_priv *priv)
{
clrbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN);
clrbits_le32(&priv->regs->sstsa, SSP_SSTSA_EN);
}
static int broadwell_i2s_tx_data(struct udevice *dev, void *data,
uint data_size)
{
struct broadwell_i2s_priv *priv = dev_get_priv(dev);
u32 *ptr = data;
log_debug("data=%p, data_size=%x\n", data, data_size);
if (data_size < SSP_FIFO_SIZE) {
log_err("Invalid I2S data size\n");
return -ENODATA;
}
/* Enable I2S interface */
bdw_i2s_enable(priv);
/* Transfer data */
while (data_size > 0) {
ulong start = timer_get_us() + 100000;
/* Write data if transmit FIFO has room */
if (readl(&priv->regs->sssr) & SSP_SSS_TNF) {
writel(*ptr++, &priv->regs->ssdr);
data_size -= sizeof(*ptr);
} else {
if ((long)(timer_get_us() - start) > 0) {
/* Disable I2S interface */
bdw_i2s_disable(priv);
log_debug("I2S Transfer Timeout\n");
return -ETIMEDOUT;
}
}
}
/* Disable I2S interface */
bdw_i2s_disable(priv);
log_debug("done\n");
return 0;
}
static int broadwell_i2s_probe(struct udevice *dev)
{
struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct broadwell_i2s_priv *priv = dev_get_priv(dev);
struct udevice *adsp = dev_get_parent(dev);
u32 bar0, offset;
int ret;
bar0 = dm_pci_read_bar32(adsp, 0);
if (!bar0) {
log_debug("Cannot read adsp bar0\n");
return -EINVAL;
}
offset = dev_read_addr_index(dev, 0);
if (offset == FDT_ADDR_T_NONE) {
log_debug("Cannot read address index 0\n");
return -EINVAL;
}
uc_priv->base_address = bar0 + offset;
/*
* Hard-code these values. If other settings are required we can add
* this to the device tree.
*/
uc_priv->rfs = 64;
uc_priv->bfs = 32;
uc_priv->audio_pll_clk = 24 * 1000 * 1000;
uc_priv->samplingrate = 48000;
uc_priv->bitspersample = 16;
uc_priv->channels = 2;
uc_priv->id = 0;
priv->shim = (struct i2s_shim_regs *)uc_priv->base_address;
priv->sfrm_polarity = SSP_FRMS_ACTIVE_LOW;
priv->end_transfer_state = SSP_END_TRANSFER_STATE_LOW;
priv->sclk_mode = SCLK_MODE_DDF_DSR_ISL;
priv->rel_timing = NEXT_FRMS_WITH_LSB_PREVIOUS_FRM;
priv->sclk_dummy_stop = 0;
priv->sclk_frame_width = 31;
offset = dev_read_addr_index(dev, 1 + uc_priv->id);
if (offset == FDT_ADDR_T_NONE) {
log_debug("Cannot read address index %d\n", 1 + uc_priv->id);
return -EINVAL;
}
log_debug("bar0=%x, uc_priv->base_address=%x, offset=%x\n", bar0,
uc_priv->base_address, offset);
priv->regs = (struct broadwell_i2s_regs *)(bar0 + offset);
ret = bdw_i2s_init(dev);
if (ret)
return ret;
return 0;
}
static const struct i2s_ops broadwell_i2s_ops = {
.tx_data = broadwell_i2s_tx_data,
};
static const struct udevice_id broadwell_i2s_ids[] = {
{ .compatible = "intel,broadwell-i2s" },
{ }
};
U_BOOT_DRIVER(broadwell_i2s) = {
.name = "broadwell_i2s",
.id = UCLASS_I2S,
.of_match = broadwell_i2s_ids,
.probe = broadwell_i2s_probe,
.ops = &broadwell_i2s_ops,
.priv_auto_alloc_size = sizeof(struct broadwell_i2s_priv),
};
@@ -0,0 +1,301 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Intel Broadwell I2S driver
*
* Copyright 2019 Google LLC
*
* Modified from dc i2s/broadwell/broadwell.h
*/
#ifndef __BROADWELL_I2S_H__
#define __BROADWELL_I2S_H__
enum {
SSP_FIFO_SIZE = 7,
};
enum frame_sync_rel_timing_t {
NEXT_FRMS_AFTER_END_OF_T4 = 0,
NEXT_FRMS_WITH_LSB_PREVIOUS_FRM,
};
enum frame_sync_pol_t {
SSP_FRMS_ACTIVE_LOW = 0,
SSP_FRMS_ACTIVE_HIGH,
};
enum end_transfer_state_t {
SSP_END_TRANSFER_STATE_LOW = 0,
SSP_END_TRANSFER_STATE_PEVIOUS_BIT,
};
enum clock_mode_t {
/* Data driven (falling), data sampled (rising), idle state (low) */
SCLK_MODE_DDF_DSR_ISL,
/* Data driven (rising), data sampled (falling), idle state (low) */
SCLK_MODE_DDR_DSF_ISL,
/* Data driven (rising), data sampled (falling), idle state (high) */
SCLK_MODE_DDR_DSF_ISH,
/* Data driven (falling), data sampled (rising), idle state (high) */
SCLK_MODE_DDF_DSR_ISH,
};
struct i2s_shim_regs {
u32 csr; /* 0x00 */
u32 reserved0[29]; /* 0x14 - 0x77 */
u32 clkctl; /* 0x78 */
u32 reserved1; /* 0x7c */
u32 cs2; /* 0x80 */
};
struct broadwell_i2s_regs {
u32 sscr0; /* 0x00 */
u32 sscr1; /* 0x04 */
u32 sssr; /* 0x08 */
u32 ssitr; /* 0x0c */
u32 ssdr; /* 0x10 */
u32 reserved0[5]; /* 0x14 - 0x27 */
u32 ssto; /* 0x28 */
u32 sspsp; /* 0x2c */
u32 sstsa; /* 0x30 */
u32 ssrsa; /* 0x34 */
u32 sstss; /* 0x38 */
u32 sscr2; /* 0x40 */
u32 sspsp2; /* 0x44 */
};
/* SHIM Configuration & Status */
enum {
/* Low Power Clock Select */
SHIM_CS_LPCS = 1 << 31,
/* SSP Force Clock Running */
SHIM_CS_SFCR_SSP1 = 1 << 28,
SHIM_CS_SFCR_SSP0 = 1 << 27,
/* SSP1 IO Clock Select */
SHIM_CS_S1IOCS = 1 << 23,
/* SSP0 IO Clock Select */
SHIM_CS_S0IOCS = 1 << 21,
/* Parity Check Enable */
SHIM_CS_PCE = 1 << 15,
/* SSP DMA or PIO Mode */
SHIM_CS_SDPM_PIO_SSP1 = 1 << 12,
SHIM_CS_SDPM_DMA_SSP1 = 0 << 12,
SHIM_CS_SDPM_PIO_SSP0 = 1 << 11,
SHIM_CS_SDPM_DMA_SSP0 = 0 << 11,
/* Run / Stall */
SHIM_CS_STALL = 1 << 10,
/* DSP Clock Select */
SHIM_CS_DCS_DSP320_AF80 = 0 << 4,
SHIM_CS_DCS_DSP160_AF80 = 1 << 4,
SHIM_CS_DCS_DSP80_AF80 = 2 << 4,
SHIM_CS_DCS_DSP320_AF160 = 4 << 4,
SHIM_CS_DCS_DSP160_AF160 = 5 << 4,
SHIM_CS_DCS_DSP32_AF32 = 6 << 4,
SHIM_CS_DCS_MASK = 7 << 4,
/* SSP Base Clock Select */
SHIM_CS_SBCS_SSP0_24MHZ = 1 << 3,
SHIM_CS_SBCS_SSP0_32MHZ = 0 << 3,
SHIM_CS_SBCS_SSP1_24MHZ = 1 << 2,
SHIM_CS_SBCS_SSP1_32MHZ = 0 << 2,
/* DSP Core Reset */
SHIM_CS_RST = 1 << 1,
};
/* SHIM Clock Control */
enum {
/* Clock Frequency Change In Progress */
SHIM_CLKCTL_CFCIP = 1 << 31,
/* SSP MCLK Output Select */
SHIM_CLKCTL_MCLK_MASK = 0x3,
SHIM_CLKCTL_MCLK_SHIFT = 24,
SHIM_CLKCTL_MCLK_DISABLED = 0 << 24,
SHIM_CLKCTL_MCLK_6MHZ = 1 << 24,
SHIM_CLKCTL_MCLK_12MHZ = 2 << 24,
SHIM_CLKCTL_MCLK_24MHZ = 3 << 24,
/* DSP Core Prevent Local Clock Gating */
SHIM_CLKCTL_DCPLCG = 1 << 18,
/* SSP Clock Output Enable */
SHIM_CLKCTL_SCOE_SSP1 = 1 << 17,
SHIM_CLKCTL_SCOE_SSP0 = 1 << 16,
/* DMA Engine Force Local Clock Gating */
SHIM_CLKCTL_DEFLCGB_DMA1_CGE = 0 << 6,
SHIM_CLKCTL_DEFLCGB_DMA1_CGD = 1 << 6,
SHIM_CLKCTL_DEFLCGB_DMA0_CGE = 0 << 5,
SHIM_CLKCTL_DEFLCGB_DMA0_CGD = 1 << 5,
/* SSP Force Local Clock Gating */
SHIM_CLKCTL_SFLCGB_SSP1_CGE = 0 << 1,
SHIM_CLKCTL_SFLCGB_SSP1_CGD = 1 << 1,
SHIM_CLKCTL_SFLCGB_SSP0_CGE = 0 << 0,
SHIM_CLKCTL_SFLCGB_SSP0_CGD = 1 << 0,
/* Reserved bits: 30:26, 23:19, 15:7, 4:2 */
SHIM_CLKCTL_RESERVED = 0x1f << 26 | 0x1f << 19 | 0x1ff << 7 | 0x7 << 2,
};
/* SSP Status */
enum {
/* Bit Count Error */
SSP_SSS_BCE = 1 << 23,
/* Clock Sync Statu s*/
SSP_SSS_CSS = 1 << 22,
/* Transmit FIFO Underrun */
SSP_SSS_TUR = 1 << 21,
/* End Of Chain */
SSP_SSS_EOC = 1 << 20,
/* Receiver Time-out Interrupt */
SSP_SSS_TINT = 1 << 19,
/* Peripheral Trailing Byte Interrupt */
SSP_SSS_PINT = 1 << 18,
/* Received FIFO Level */
SSP_RFL_MASK = 0xf,
SSP_RFL_SHIFT = 12,
/* Transmit FIFO Level */
SSP_TFL_MASK = 0xf,
SSP_TFL_SHIFT = 8,
/* Receive FIFO Overrun */
SSP_SSS_ROR = 1 << 7,
/* Receive FIFO Service Request */
SSP_SSS_RFS = 1 << 6,
/* Transmit FIFO Service Request */
SSP_SSS_TFS = 1 << 5,
/* SSP Busy */
SSP_SSS_BSY = 1 << 4,
/* Receive FIFO Not Empty */
SSP_SSS_RNE = 1 << 3,
/* Transmit FIFO Not Full */
SSP_SSS_TNF = 1 << 2,
};
/* SSP Control 0 */
enum {
/* Mode */
SSP_SSC0_MODE_NORMAL = 0 << 31,
SSP_SSC0_MODE_NETWORK = 1 << 31,
/* Audio Clock Select */
SSP_SSC0_ACS_PCH = 0 << 30,
/* Frame Rate Divider Control (0-7) */
SSP_SSC0_FRDC_MASK = 0x7,
SSP_SSC0_FRDC_SHIFT = 24,
SSP_SSC0_FRDC_STEREO = 1 << 24,
/* Transmit FIFO Underrun Interrupt Mask */
SSP_SSC0_TIM = 1 << 23,
/* Receive FIFO Underrun Interrupt Mask */
SSP_SSC0_RIM = 1 << 22,
/* Network Clock Select */
SSP_SSC0_NCS_PCH = 0 << 21,
/* Extended Data Size Select */
SSP_SSC0_EDSS = 1 << 20,
/* Serial Clock Rate (0-4095) */
SSP_SSC0_SCR_SHIFT = 8,
SSP_SSC0_SCR_MASK = 0xfff << SSP_SSC0_SCR_SHIFT,
/* Synchronous Serial Port Enable */
SSP_SSC0_SSE = 1 << 7,
/* External Clock Select */
SSP_SSC0_ECS_PCH = 0 << 6,
/* Frame Format */
SSP_SSC0_FRF_MOTOROLA_SPI = 0 << 4,
SSP_SSC0_FRF_TI_SSP = 1 << 4,
SSP_SSC0_FRF_NS_MICROWIRE = 2 << 4,
SSP_SSC0_FRF_PSP = 3 << 4,
/* Data Size Select */
SSP_SSC0_DSS_SHIFT = 0,
SSP_SSC0_DSS_MASK = 0xf << SSP_SSC0_DSS_SHIFT,
};
/* SSP Control 1 */
enum {
/* TXD Tristate Enable on Last Phase */
SSP_SSC1_TTELP = 1 << 31,
/* TXD Tristate Enable */
SSP_SSC1_TTE = 1 << 30,
/* Enable Bit Count Error Interrupt */
SSP_SSC1_EBCEI = 1 << 29,
/* Slave Clock Running */
SSP_SSC1_SCFR = 1 << 28,
/* Enable Clock Request A */
SSP_SSC1_ECRA = 1 << 27,
/* Enable Clock Request B */
SSP_SSC1_ECRB = 1 << 26,
/* SSPCLK Direction */
SSP_SSC1_SCLKDIR_SLAVE = 1 << 25,
SSP_SSC1_SCLKDIR_MASTER = 0 << 25,
/* SSPFRM Direction */
SSP_SSC1_SFRMDIR_SLAVE = 1 << 24,
SSP_SSC1_SFRMDIR_MASTER = 0 << 24,
/* Receive without Transmit */
SSP_SSC1_RWOT = 1 << 23,
/* Trailing Byte */
SSP_SSC1_TRAIL = 1 << 22,
/* DMA Tx Service Request Enable */
SSP_SSC1_TSRE = 1 << 21,
/* DMA Rx Service Request Enable */
SSP_SSC1_RSRE = 1 << 20,
/* Receiver Timeout Interrupt Enable */
SSP_SSC1_TINTE = 1 << 19,
/* Periph. Trailing Byte Int. Enable */
SSP_SSC1_PINTE = 1 << 18,
/* Invert Frame Signal */
SSP_SSC1_IFS = 1 << 16,
/* Select FIFO for EFWR: test mode */
SSP_SSC1_STRF = 1 << 15,
/* Enable FIFO Write/Read: test mode */
SSP_SSC1_EFWR = 1 << 14,
/* Receive FIFO Trigger Threshold */
SSP_SSC1_RFT_SHIFT = 10,
SSP_SSC1_RFT_MASK = 0xf << SSP_SSC1_RFT_SHIFT,
/* Transmit FIFO Trigger Threshold */
SSP_SSC1_TFT_SHIFT = 6,
SSP_SSC1_TFT_MASK = 0xf << SSP_SSC1_TFT_SHIFT,
/* Microwire Transmit Data Size */
SSP_SSC1_MWDS = 1 << 5,
/* Motorola SPI SSPSCLK Phase Setting*/
SSP_SSC1_SPH = 1 << 4,
/* Motorola SPI SSPSCLK Polarity */
SSP_SSC1_SPO = 1 << 3,
/* Loopback mode: test mode */
SSP_SSC1_LBM = 1 << 2,
/* Transmit FIFO Interrupt Enable */
SSP_SSC1_TIE = 1 << 1,
/* Receive FIFO Interrupt Enable */
SSP_SSC1_RIE = 1 << 0,
SSP_SSC1_RESERVED = 17 << 1,
};
/* SSP Programmable Serial Protocol */
enum {
/* Extended Dummy Stop (0-31) */
SSP_PSP_EDYMSTOP_SHIFT = 26,
SSP_PSP_EDMYSTOP_MASK = 0x7 << SSP_PSP_EDYMSTOP_SHIFT,
/* Frame Sync Relative Timing */
SSP_PSP_FSRT = 1 << 25,
/* Dummy Stop low bits */
SSP_PSP_DMYSTOP_SHIFT = 23,
SSP_PSP_DMYSTOP_MASK = 0x3 << SSP_PSP_DMYSTOP_SHIFT,
/* Serial Frame Width */
SSP_PSP_SFRMWDTH_SHIFT = 16,
SSP_PSP_SFRMWDTH_MASK = 0x3f << SSP_PSP_SFRMWDTH_SHIFT,
/* Serial Frame Delay */
SSP_PSP_SFRMDLY_MASK = 0x7f,
SSP_PSP_SFRMDLY_SHIFT = 9,
/* Start Delay */
SSP_PSP_STRTDLY_MASK = 0x7,
SSP_PSP_STRTDLY_SHIFT = 4,
/* End of Transfer Data State */
SSP_PSP_ETDS = 1 << 3,
/* Serial Frame Polarity */
SSP_PSP_SFRMP = 1 << 2,
/* Serial Clock Mode */
SSP_PSP_SCMODE_SHIFT = 0,
SSP_PSP_SCMODE_MASK = 0x3 << SSP_PSP_SCMODE_SHIFT,
SSP_PSP_RESERVED = 1 << 22,
};
/* SSP TX Time Slot Active */
enum {
SSP_SSTSA_EN = 1 << 8,
SSP_SSTSA_MASK = 0xff,
};
#endif /* __BROADWELL_I2S_H__ */
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Sound for broadwell
*
* Copyright 2019 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
static int broadwell_sound_probe(struct udevice *dev)
{
return sound_find_codec_i2s(dev);
}
static int broadwell_sound_setup(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct i2s_uc_priv *i2c_priv = dev_get_uclass_priv(uc_priv->i2s);
int ret;
if (uc_priv->setup_done)
return -EALREADY;
ret = audio_codec_set_params(uc_priv->codec, i2c_priv->id,
i2c_priv->samplingrate,
i2c_priv->samplingrate * i2c_priv->rfs,
i2c_priv->bitspersample,
i2c_priv->channels);
if (ret)
return ret;
uc_priv->setup_done = true;
return 0;
}
static int broadwell_sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
return i2s_tx_data(uc_priv->i2s, data, data_size);
}
static const struct sound_ops broadwell_sound_ops = {
.setup = broadwell_sound_setup,
.play = broadwell_sound_play,
};
static const struct udevice_id broadwell_sound_ids[] = {
{ .compatible = "google,samus-sound" },
{ }
};
U_BOOT_DRIVER(broadwell_sound_drv) = {
.name = "broadwell_sound",
.id = UCLASS_SOUND,
.of_match = broadwell_sound_ids,
.probe = broadwell_sound_probe,
.ops = &broadwell_sound_ops,
};
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <audio_codec.h>
int audio_codec_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample, uint channels)
{
struct audio_codec_ops *ops = audio_codec_get_ops(dev);
if (!ops->set_params)
return -ENOSYS;
return ops->set_params(dev, interface, rate, mclk_freq, bits_per_sample,
channels);
}
UCLASS_DRIVER(audio_codec) = {
.id = UCLASS_AUDIO_CODEC,
.name = "audio-codec",
};
@@ -0,0 +1,556 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Implementation of per-board codec beeping
* Copyright (c) 2011 The Chromium OS Authors.
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <dm.h>
#include <hda_codec.h>
#include <pci.h>
#include <sound.h>
#include <asm/io.h>
#include <dt-bindings/sound/azalia.h>
/**
* struct hda_regs - HDA registers
*
* https://wiki.osdev.org/Intel_High_Definition_Audio
* https://www.intel.com/content/www/us/en/standards/high-definition-audio-specification.html
*/
struct hda_regs {
u16 gcap;
u8 vmin;
u8 vmaj;
u16 outpay;
u16 inpay;
u32 gctl;
u16 wakeen;
u16 statests;
u8 reserved[0x50];
u32 cmd; /* 0x60 */
u32 resp;
u32 icii;
};
enum {
HDA_ICII_BUSY = BIT(0),
HDA_ICII_VALID = BIT(1),
/* Common node IDs */
HDA_ROOT_NODE = 0x00,
/* HDA verbs fields */
HDA_VERB_NID_S = 20,
HDA_VERB_VERB_S = 8,
HDA_VERB_PARAM_S = 0,
HDA_VERB_GET_PARAMS = 0xf00,
HDA_VERB_SET_BEEP = 0x70a,
/* GET_PARAMS parameter IDs */
GET_PARAMS_NODE_COUNT = 0x04,
GET_PARAMS_AUDIO_GROUP_CAPS = 0x08,
GET_PARAMS_AUDIO_WIDGET_CAPS = 0x09,
/* Sub-node fields */
NUM_SUB_NODES_S = 0,
NUM_SUB_NODES_M = 0xff << NUM_SUB_NODES_S,
FIRST_SUB_NODE_S = 16,
FIRST_SUB_NODE_M = 0xff << FIRST_SUB_NODE_S,
/* Get Audio Function Group Capabilities fields */
AUDIO_GROUP_CAPS_BEEP_GEN = 0x10000,
/* Get Audio Widget Capabilities fields */
AUDIO_WIDGET_TYPE_BEEP = 0x7,
AUDIO_WIDGET_TYPE_S = 20,
AUDIO_WIDGET_TYPE_M = 0xf << AUDIO_WIDGET_TYPE_S,
BEEP_FREQ_BASE = 12000,
};
static inline uint hda_verb(uint nid, uint verb, uint param)
{
return nid << HDA_VERB_NID_S | verb << HDA_VERB_VERB_S |
param << HDA_VERB_PARAM_S;
}
int hda_wait_for_ready(struct hda_regs *regs)
{
int timeout = 1000; /* Use a 1msec timeout */
while (timeout--) {
u32 reg32 = readl(&regs->icii);
if (!(reg32 & HDA_ICII_BUSY))
return 0;
udelay(1);
}
return -ETIMEDOUT;
}
static int wait_for_response(struct hda_regs *regs, uint *response)
{
int timeout = 1000;
u32 reg32;
/* Send the verb to the codec */
setbits_le32(&regs->icii, HDA_ICII_BUSY | HDA_ICII_VALID);
/* Use a 1msec timeout */
while (timeout--) {
reg32 = readl(&regs->icii);
if ((reg32 & (HDA_ICII_VALID | HDA_ICII_BUSY)) ==
HDA_ICII_VALID) {
if (response)
*response = readl(&regs->resp);
return 0;
}
udelay(1);
}
return -ETIMEDOUT;
}
int hda_wait_for_valid(struct hda_regs *regs)
{
return wait_for_response(regs, NULL);
}
static int set_bits(void *port, u32 mask, u32 val)
{
u32 reg32;
int count;
/* Write (val & mask) to port */
clrsetbits_le32(port, mask, val);
/* Wait for readback of register to match what was just written to it */
count = 50;
do {
/* Wait 1ms based on BKDG wait time */
mdelay(1);
reg32 = readl(port) & mask;
} while (reg32 != val && --count);
/* Timeout occurred */
if (!count)
return -ETIMEDOUT;
return 0;
}
int hda_codec_detect(struct hda_regs *regs)
{
uint reg8;
/* Set Bit 0 to 1 to exit reset state (BAR + 0x8)[0] */
if (set_bits(&regs->gctl, 1, 1))
goto no_codec;
/* Write back the value once reset bit is set */
writew(readw(&regs->gcap), &regs->gcap);
/* Read in Codec location */
reg8 = readb(&regs->statests) & 0xf;
if (!reg8)
goto no_codec;
return reg8;
no_codec:
/* Codec Not found - put HDA back in reset */
set_bits(&regs->gctl, 1, 0);
log_debug("No codec\n");
return 0;
}
static int find_verb_data(struct udevice *dev, uint id, ofnode *nodep)
{
ofnode parent = dev_read_subnode(dev, "codecs");
ofnode node;
u32 vendor_id, device_id;
ofnode_for_each_subnode(node, parent) {
if (ofnode_read_u32(node, "vendor-id", &vendor_id) ||
ofnode_read_u32(node, "device-id", &device_id)) {
log_debug("Cannot get IDs for '%s'\n",
ofnode_get_name(node));
return -EINVAL;
}
if (id != (vendor_id << 16 | device_id)) {
log_debug("Skip codec node '%s' for %08x\n",
ofnode_get_name(node), id);
continue;
}
log_debug("Found codec node '%s' for %08x\n",
ofnode_get_name(node), id);
*nodep = node;
return 0;
}
return -ENOENT;
}
static int send_verbs(ofnode node, const char *prop_name, struct hda_regs *regs)
{
int ret, verb_size, i;
const u32 *verb;
verb = ofnode_get_property(node, prop_name, &verb_size);
if (verb_size < 0) {
log_debug("No verb data\n");
return -EINVAL;
}
log_debug("verb_size: %d\n", verb_size);
for (i = 0; i < verb_size / sizeof(*verb); i++) {
ret = hda_wait_for_ready(regs);
if (ret) {
log_debug(" codec ready timeout\n");
return ret;
}
writel(fdt32_to_cpu(verb[i]), &regs->cmd);
ret = hda_wait_for_valid(regs);
if (ret) {
log_debug(" codec valid timeout\n");
return ret;
}
}
return 0;
}
static int codec_init(struct udevice *dev, struct hda_regs *regs, uint addr)
{
ofnode node;
uint id;
int ret;
log_debug("Initializing codec #%d\n", addr);
ret = hda_wait_for_ready(regs);
if (ret) {
log_debug(" codec not ready\n");
return ret;
}
/* Read the codec's vendor ID */
writel(addr << AZALIA_CODEC_SHIFT |
AZALIA_OPCODE_READ_PARAM << AZALIA_VERB_SHIFT |
AZALIA_PARAM_VENDOR_ID, &regs->cmd);
ret = hda_wait_for_valid(regs);
if (ret) {
log_debug(" codec not valid\n");
return ret;
}
id = readl(&regs->resp);
log_debug("codec vid/did: %08x\n", id);
ret = find_verb_data(dev, id, &node);
if (ret) {
log_debug("No verb (err=%d)\n", ret);
return ret;
}
ret = send_verbs(node, "verbs", regs);
if (ret) {
log_debug("failed to send verbs (err=%d)\n", ret);
return ret;
}
log_debug("verb loaded\n");
return 0;
}
int hda_codecs_init(struct udevice *dev, struct hda_regs *regs, u32 codec_mask)
{
int ret;
int i;
for (i = 3; i >= 0; i--) {
if (codec_mask & (1 << i)) {
ret = codec_init(dev, regs, i);
if (ret)
return ret;
}
}
ret = send_verbs(dev_ofnode(dev), "beep-verbs", regs);
if (ret) {
log_debug("failed to send beep verbs (err=%d)\n", ret);
return ret;
}
log_debug("beep verbs loaded\n");
return 0;
}
/**
* exec_verb() - Write a verb to the codec
*
* @regs: HDA registers
* @val: Command to write
* @response: Set to response from codec
* @return 0 if OK, -ve on error
*/
static int exec_verb(struct hda_regs *regs, uint val, uint *response)
{
int ret;
ret = hda_wait_for_ready(regs);
if (ret)
return ret;
writel(val, &regs->cmd);
return wait_for_response(regs, response);
}
/**
* get_subnode_info() - Get subnode information
*
* @regs: HDA registers
* @nid: Parent node ID to check
* @num_sub_nodesp: Returns number of subnodes
* @start_sub_node_nidp: Returns start subnode number
* @return 0 if OK, -ve on error
*/
static int get_subnode_info(struct hda_regs *regs, uint nid,
uint *num_sub_nodesp, uint *start_sub_node_nidp)
{
uint response;
int ret;
ret = exec_verb(regs, hda_verb(nid, HDA_VERB_GET_PARAMS,
GET_PARAMS_NODE_COUNT),
&response);
if (ret < 0) {
printf("Audio: Error reading sub-node info %d\n", nid);
return ret;
}
*num_sub_nodesp = (response & NUM_SUB_NODES_M) >> NUM_SUB_NODES_S;
*start_sub_node_nidp = (response & FIRST_SUB_NODE_M) >>
FIRST_SUB_NODE_S;
return 0;
}
/**
* find_beep_node_in_group() - Finds the beeping node
*
* Searches the audio group for a node that supports beeping
*
* @regs: HDA registers
* @group_nid: Group node ID to check
* @return 0 if OK, -ve on error
*/
static uint find_beep_node_in_group(struct hda_regs *regs, uint group_nid)
{
uint node_count = 0;
uint current_nid = 0;
uint response;
uint end_nid;
int ret;
ret = get_subnode_info(regs, group_nid, &node_count, &current_nid);
if (ret < 0)
return 0;
end_nid = current_nid + node_count;
while (current_nid < end_nid) {
ret = exec_verb(regs,
hda_verb(current_nid, HDA_VERB_GET_PARAMS,
GET_PARAMS_AUDIO_WIDGET_CAPS),
&response);
if (ret < 0) {
printf("Audio: Error reading widget caps\n");
return 0;
}
if ((response & AUDIO_WIDGET_TYPE_M) >> AUDIO_WIDGET_TYPE_S ==
AUDIO_WIDGET_TYPE_BEEP)
return current_nid;
current_nid++;
}
return 0; /* no beep node found */
}
/**
* audio_group_has_beep_node() - Check if group has a beep node
*
* Checks if the given audio group contains a beep generator
* @regs: HDA registers
* @nid: Node ID to check
* @return 0 if OK, -ve on error
*/
static int audio_group_has_beep_node(struct hda_regs *regs, uint nid)
{
uint response;
int ret;
ret = exec_verb(regs, hda_verb(nid, HDA_VERB_GET_PARAMS,
GET_PARAMS_AUDIO_GROUP_CAPS),
&response);
if (ret < 0) {
printf("Audio: Error reading audio group caps %d\n", nid);
return 0;
}
return !!(response & AUDIO_GROUP_CAPS_BEEP_GEN);
}
/**
* get_hda_beep_nid() - Finds the node ID of the beep node
*
* Finds the nid of the beep node if it exists. Starts at the root node, for
* each sub-node checks if the group contains a beep node. If the group
* contains a beep node, polls each node in the group until it is found.
*
* If the device has a intel,beep-nid property, the value of that is used
* instead.
*
* @dev: Sound device
* @return Node ID >0 if found, -ve error code otherwise
*/
static int get_hda_beep_nid(struct udevice *dev)
{
struct hda_codec_priv *priv = dev_get_priv(dev);
uint current_nid = 0;
uint node_count = 0;
uint end_nid;
int ret;
/* If the field exists, use the beep nid set in the fdt */
ret = dev_read_u32(dev, "intel,beep-nid", &current_nid);
if (!ret)
return current_nid;
ret = get_subnode_info(priv->regs, HDA_ROOT_NODE, &node_count,
&current_nid);
if (ret < 0)
return ret;
end_nid = current_nid + node_count;
while (current_nid < end_nid) {
if (audio_group_has_beep_node(priv->regs, current_nid))
return find_beep_node_in_group(priv->regs,
current_nid);
current_nid++;
}
/* no beep node found */
return -ENOENT;
}
/**
* set_beep_divisor() - Sets the beep divisor to set the pitch
*
* @priv: Device's private data
* @divider: Divider value (0 to disable the beep)
* @return 0 if OK, -ve on error
*/
static int set_beep_divisor(struct hda_codec_priv *priv, uint divider)
{
return exec_verb(priv->regs,
hda_verb(priv->beep_nid, HDA_VERB_SET_BEEP, divider),
NULL);
}
int hda_codec_init(struct udevice *dev)
{
struct hda_codec_priv *priv = dev_get_priv(dev);
ulong base_addr;
base_addr = dm_pci_read_bar32(dev, 0);
log_debug("base = %08lx\n", base_addr);
if (!base_addr)
return -EINVAL;
priv->regs = (struct hda_regs *)base_addr;
return 0;
}
int hda_codec_finish_init(struct udevice *dev)
{
struct hda_codec_priv *priv = dev_get_priv(dev);
int ret;
ret = get_hda_beep_nid(dev);
if (ret <= 0) {
log_warning("Could not find beep NID (err=%d)\n", ret);
return ret ? ret : -ENOENT;
}
priv->beep_nid = ret;
return 0;
}
int hda_codec_start_beep(struct udevice *dev, int frequency_hz)
{
struct hda_codec_priv *priv = dev_get_priv(dev);
uint divider_val;
if (!priv->beep_nid) {
log_err("Failed to find a beep-capable node\n");
return -ENOENT;
}
if (!frequency_hz)
divider_val = 0; /* off */
else if (frequency_hz > BEEP_FREQ_BASE)
divider_val = 1;
else if (frequency_hz < BEEP_FREQ_BASE / 0xff)
divider_val = 0xff;
else
divider_val = 0xff & (BEEP_FREQ_BASE / frequency_hz);
return set_beep_divisor(priv, divider_val);
}
int hda_codec_stop_beep(struct udevice *dev)
{
struct hda_codec_priv *priv = dev_get_priv(dev);
return set_beep_divisor(priv, 0);
}
static const struct sound_ops hda_codec_ops = {
.setup = hda_codec_finish_init,
.start_beep = hda_codec_start_beep,
.stop_beep = hda_codec_stop_beep,
};
U_BOOT_DRIVER(hda_codec) = {
.name = "hda_codec",
.id = UCLASS_SOUND,
.ops = &hda_codec_ops,
.priv_auto_alloc_size = sizeof(struct hda_codec_priv),
.probe = hda_codec_init,
};
static struct pci_device_id hda_supported[] = {
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_HDA},
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_WILDCATPOINT_HDA) },
/*
* Note this driver is not necessarily generic, but it attempts to
* support any codec in the hd-audio class
*/
{ PCI_DEVICE_CLASS(PCI_CLASS_MULTIMEDIA_HD_AUDIO, 0xffffff) },
};
U_BOOT_PCI_DEVICE(hda_codec, hda_supported);
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <i2s.h>
int i2s_tx_data(struct udevice *dev, void *data, uint data_size)
{
struct i2s_ops *ops = i2s_get_ops(dev);
if (!ops->tx_data)
return -ENOSYS;
return ops->tx_data(dev, data, data_size);
}
UCLASS_DRIVER(i2s) = {
.id = UCLASS_I2S,
.name = "i2s",
.per_device_auto_alloc_size = sizeof(struct i2s_uc_priv),
};
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
*/
#include <common.h>
#include <dm.h>
#include <sound.h>
#include <asm/i8254.h>
int i8254_start_beep(struct udevice *dev, int frequency_hz)
{
return i8254_enable_beep(frequency_hz);
}
int i8254_stop_beep(struct udevice *dev)
{
i8254_disable_beep();
return 0;
}
static const struct sound_ops i8254_ops = {
.start_beep = i8254_start_beep,
.stop_beep = i8254_stop_beep,
};
static const struct udevice_id i8254_ids[] = {
{ .compatible = "i8254,beeper" },
{ }
};
U_BOOT_DRIVER(i8254_drv) = {
.name = "i8254_drv",
.id = UCLASS_SOUND,
.of_match = i8254_ids,
.ops = &i8254_ops,
};
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel HDA audio (Azalia) for ivybridge
*
* Originally from coreboot file bd82x6x/azalia.c
*
* Copyright (C) 2008 Advanced Micro Devices, Inc.
* Copyright (C) 2008-2009 coresystems GmbH
* Copyright (C) 2011 The ChromiumOS Authors.
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <dm.h>
#include <hda_codec.h>
#include <pch.h>
#include <sound.h>
static int bd82x6x_azalia_probe(struct udevice *dev)
{
struct pci_child_platdata *plat;
struct hda_codec_priv *priv;
struct udevice *pch;
u32 codec_mask;
int conf;
int ret;
/* Only init after relocation */
if (!(gd->flags & GD_FLG_RELOC))
return 0;
ret = hda_codec_init(dev);
if (ret) {
log_debug("Cannot set up HDA codec (err=%d)\n", ret);
return ret;
}
priv = dev_get_priv(dev);
ret = uclass_first_device_err(UCLASS_PCH, &pch);
log_debug("PCH %p %s\n", pch, pch->name);
if (ret)
return ret;
conf = pch_ioctl(pch, PCH_REQ_HDA_CONFIG, NULL, 0);
log_debug("conf = %x\n", conf);
if (conf >= 0) {
dm_pci_clrset_config32(dev, 0x120, 7 << 24 | 0xfe,
1 << 24 | /* 2 << 24 for server */
conf);
dm_pci_clrset_config16(dev, 0x78, 0, 1 << 1);
} else {
log_debug("V1CTL disabled\n");
}
dm_pci_clrset_config32(dev, 0x114, 0xfe, 0);
/* Set VCi enable bit */
dm_pci_clrset_config32(dev, 0x120, 0, 1U << 31);
/* Enable HDMI codec */
dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 1);
dm_pci_clrset_config8(dev, 0x43, 0, 1 << 6);
/* Additional programming steps */
dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 13);
dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 10);
dm_pci_clrset_config32(dev, 0xd0, 1U << 31, 0);
/* Additional step on Panther Point */
plat = dev_get_parent_platdata(dev);
if (plat->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA)
dm_pci_clrset_config32(dev, 0xc4, 0, 1 << 17);
dm_pci_write_config8(dev, 0x3c, 0xa); /* unused? */
/* Audio Control: Select Azalia mode */
dm_pci_clrset_config8(dev, 0x40, 0, 1);
dm_pci_clrset_config8(dev, 0x4d, 1 << 7, 0); /* Docking not supported */
codec_mask = hda_codec_detect(priv->regs);
log_debug("codec_mask = %02x\n", codec_mask);
if (codec_mask) {
ret = hda_codecs_init(dev, priv->regs, codec_mask);
if (ret) {
log_err("Codec init failed (err=%d)\n", ret);
return ret;
}
}
/* Enable dynamic clock gating */
dm_pci_clrset_config8(dev, 0x43, 7, BIT(2) | BIT(0));
ret = hda_codec_finish_init(dev);
if (ret) {
log_debug("Cannot set up HDA codec (err=%d)\n", ret);
return ret;
}
return 0;
}
static int bd82x6x_azalia_setup(struct udevice *dev)
{
return 0;
}
int bd82x6x_azalia_start_beep(struct udevice *dev, int frequency_hz)
{
return hda_codec_start_beep(dev, frequency_hz);
}
int bd82x6x_azalia_stop_beep(struct udevice *dev)
{
return hda_codec_stop_beep(dev);
}
static const struct sound_ops bd82x6x_azalia_ops = {
.setup = bd82x6x_azalia_setup,
.start_beep = bd82x6x_azalia_start_beep,
.stop_beep = bd82x6x_azalia_stop_beep,
};
static const struct udevice_id bd82x6x_azalia_ids[] = {
{ .compatible = "intel,hd-audio" },
{ }
};
U_BOOT_DRIVER(bd82x6x_azalia_drv) = {
.name = "bd82x6x-hda",
.id = UCLASS_SOUND,
.of_match = bd82x6x_azalia_ids,
.probe = bd82x6x_azalia_probe,
.ops = &bd82x6x_azalia_ops,
.priv_auto_alloc_size = sizeof(struct hda_codec_priv),
};
@@ -0,0 +1,424 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* max98088.c -- MAX98088 ALSA SoC Audio driver
*
* Copyright 2010 Maxim Integrated Products
*
* Modified for U-Boot by Chih-Chung Chang (chihchung@chromium.org),
* following the changes made in max98095.c
*/
#include <common.h>
#include <audio_codec.h>
#include <div64.h>
#include <dm.h>
#include <i2c.h>
#include <i2s.h>
#include <sound.h>
#include <asm/gpio.h>
#include "maxim_codec.h"
#include "max98088.h"
/* codec mclk clock divider coefficients. Index 0 is reserved. */
static const int rate_table[] = {0, 8000, 11025, 16000, 22050, 24000, 32000,
44100, 48000, 88200, 96000};
/*
* codec mclk clock divider coefficients based on sampling rate
*
* @param rate sampling rate
* @param value address of indexvalue to be stored
*
* @return 0 for success or negative error code.
*/
static int rate_value(int rate, u8 *value)
{
int i;
for (i = 1; i < ARRAY_SIZE(rate_table); i++) {
if (rate_table[i] >= rate) {
*value = i;
return 0;
}
}
*value = 1;
return -EINVAL;
}
/*
* Sets hw params for max98088
*
* @priv: max98088 information pointer
* @rate: Sampling rate
* @bits_per_sample: Bits per sample
*
* @return -EIO for error, 0 for success.
*/
int max98088_hw_params(struct maxim_priv *priv, unsigned int rate,
unsigned int bits_per_sample)
{
int error;
u8 regval;
switch (bits_per_sample) {
case 16:
error = maxim_bic_or(priv, M98088_REG_DAI1_FORMAT,
M98088_DAI_WS, 0);
break;
case 24:
error = maxim_bic_or(priv, M98088_REG_DAI1_FORMAT,
M98088_DAI_WS, M98088_DAI_WS);
break;
default:
debug("%s: Illegal bits per sample %d.\n",
__func__, bits_per_sample);
return -EINVAL;
}
error |= maxim_bic_or(priv, M98088_REG_PWR_SYS, M98088_SHDNRUN, 0);
if (rate_value(rate, &regval)) {
debug("%s: Failed to set sample rate to %d.\n",
__func__, rate);
return -EIO;
}
error |= maxim_bic_or(priv, M98088_REG_DAI1_CLKMODE,
M98088_CLKMODE_MASK, regval << 4);
priv->rate = rate;
/* Update sample rate mode */
if (rate < 50000)
error |= maxim_bic_or(priv, M98088_REG_DAI1_FILTERS,
M98088_DAI_DHF, 0);
else
error |= maxim_bic_or(priv, M98088_REG_DAI1_FILTERS,
M98088_DAI_DHF, M98088_DAI_DHF);
error |= maxim_bic_or(priv, M98088_REG_PWR_SYS, M98088_SHDNRUN,
M98088_SHDNRUN);
if (error < 0) {
debug("%s: Error setting hardware params.\n", __func__);
return -EIO;
}
priv->rate = rate;
return 0;
}
/*
* Configures Audio interface system clock for the given frequency
*
* @priv: max98088 information
* @freq: Sampling frequency in Hz
*
* @return -EIO for error, 0 for success.
*/
int max98088_set_sysclk(struct maxim_priv *priv, unsigned int freq)
{
int error = 0;
u8 pwr;
/* Requested clock frequency is already setup */
if (freq == priv->sysclk)
return 0;
/*
* Setup clocks for slave mode, and using the PLL
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
* 0x02 (when master clk is 20MHz to 30MHz)..
*/
if (freq >= 10000000 && freq < 20000000) {
error = maxim_i2c_write(priv, M98088_REG_SYS_CLK, 0x10);
} else if ((freq >= 20000000) && (freq < 30000000)) {
error = maxim_i2c_write(priv, M98088_REG_SYS_CLK, 0x20);
} else {
debug("%s: Invalid master clock frequency\n", __func__);
return -EIO;
}
error |= maxim_i2c_read(priv, M98088_REG_PWR_SYS, &pwr);
if (pwr & M98088_SHDNRUN) {
error |= maxim_bic_or(priv, M98088_REG_PWR_SYS,
M98088_SHDNRUN, 0);
error |= maxim_bic_or(priv, M98088_REG_PWR_SYS,
M98088_SHDNRUN, M98088_SHDNRUN);
}
debug("%s: Clock at %uHz\n", __func__, freq);
if (error < 0)
return -EIO;
priv->sysclk = freq;
return 0;
}
/*
* Sets Max98090 I2S format
*
* @priv: max98088 information
* @fmt: i2S format - supports a subset of the options defined in i2s.h.
*
* @return -EIO for error, 0 for success.
*/
int max98088_set_fmt(struct maxim_priv *priv, int fmt)
{
u8 reg15val;
u8 reg14val = 0;
int error = 0;
if (fmt == priv->fmt)
return 0;
priv->fmt = fmt;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* Slave mode PLL */
error |= maxim_i2c_write(priv, M98088_REG_DAI1_CLKCFG_HI,
0x80);
error |= maxim_i2c_write(priv, M98088_REG_DAI1_CLKCFG_LO,
0x00);
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* Set to master mode */
reg14val |= M98088_DAI_MAS;
break;
case SND_SOC_DAIFMT_CBS_CFM:
case SND_SOC_DAIFMT_CBM_CFS:
default:
debug("%s: Clock mode unsupported\n", __func__);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
reg14val |= M98088_DAI_DLY;
break;
case SND_SOC_DAIFMT_LEFT_J:
break;
default:
debug("%s: Unrecognized format.\n", __func__);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
reg14val |= M98088_DAI_WCI;
break;
case SND_SOC_DAIFMT_IB_NF:
reg14val |= M98088_DAI_BCI;
break;
case SND_SOC_DAIFMT_IB_IF:
reg14val |= M98088_DAI_BCI | M98088_DAI_WCI;
break;
default:
debug("%s: Unrecognized inversion settings.\n", __func__);
return -EINVAL;
}
error |= maxim_bic_or(priv, M98088_REG_DAI1_FORMAT,
M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
M98088_DAI_WCI, reg14val);
reg15val = M98088_DAI_BSEL64;
error |= maxim_i2c_write(priv, M98088_REG_DAI1_CLOCK, reg15val);
if (error < 0) {
debug("%s: Error setting i2s format.\n", __func__);
return -EIO;
}
return 0;
}
/*
* max98088_reset() - reset the audio codec
*
* @priv: max98088 information
* @return -EIO for error, 0 for success.
*/
static int max98088_reset(struct maxim_priv *priv)
{
int ret, i;
u8 val;
/*
* Reset to hardware default for registers, as there is not a soft
* reset hardware control register.
*/
for (i = M98088_REG_IRQ_ENABLE; i <= M98088_REG_PWR_SYS; i++) {
switch (i) {
case M98088_REG_BIAS_CNTL:
val = 0xf0;
break;
case M98088_REG_DAC_BIAS2:
val = 0x0f;
break;
default:
val = 0;
}
ret = maxim_i2c_write(priv, i, val);
if (ret < 0) {
debug("%s: Failed to reset: %d\n", __func__, ret);
return ret;
}
}
return 0;
}
/**
* max98088_device_init() - Initialise max98088 codec device
*
* @priv: max98088 information
*
* @return -EIO for error, 0 for success.
*/
static int max98088_device_init(struct maxim_priv *priv)
{
unsigned char id;
int error = 0;
/* reset the codec, the DSP core, and disable all interrupts */
error = max98088_reset(priv);
if (error != 0) {
debug("Reset\n");
return error;
}
/* initialize private data */
priv->sysclk = -1U;
priv->rate = -1U;
priv->fmt = -1U;
error = maxim_i2c_read(priv, M98088_REG_REV_ID, &id);
if (error < 0) {
debug("%s: Failure reading hardware revision: %d\n",
__func__, id);
return -EIO;
}
debug("%s: Hardware revision: %d\n", __func__, id);
return 0;
}
static int max98088_setup_interface(struct maxim_priv *priv)
{
int error;
/* Reading interrupt status to clear them */
error = maxim_i2c_write(priv, M98088_REG_PWR_SYS, M98088_PWRSV);
error |= maxim_i2c_write(priv, M98088_REG_IRQ_ENABLE, 0x00);
/*
* initialize registers to hardware default configuring audio
* interface2 to DAI1
*/
error |= maxim_i2c_write(priv, M98088_REG_MIX_DAC,
M98088_DAI1L_TO_DACL | M98088_DAI1R_TO_DACR);
error |= maxim_i2c_write(priv, M98088_REG_BIAS_CNTL, 0xF0);
error |= maxim_i2c_write(priv, M98088_REG_DAC_BIAS2, 0x0F);
error |= maxim_i2c_write(priv, M98088_REG_DAI1_IOCFG,
M98088_S2NORMAL | M98088_SDATA);
/*
* route DACL and DACR output to headphone and speakers
* Ordering: DACL, DACR, DACL, DACR
*/
error |= maxim_i2c_write(priv, M98088_REG_MIX_SPK_LEFT, 1);
error |= maxim_i2c_write(priv, M98088_REG_MIX_SPK_RIGHT, 1);
error |= maxim_i2c_write(priv, M98088_REG_MIX_HP_LEFT, 1);
error |= maxim_i2c_write(priv, M98088_REG_MIX_HP_RIGHT, 1);
/* set volume: -12db */
error |= maxim_i2c_write(priv, M98088_REG_LVL_SPK_L, 0x0f);
error |= maxim_i2c_write(priv, M98088_REG_LVL_SPK_R, 0x0f);
/* set volume: -22db */
error |= maxim_i2c_write(priv, M98088_REG_LVL_HP_L, 0x0d);
error |= maxim_i2c_write(priv, M98088_REG_LVL_HP_R, 0x0d);
/* power enable */
error |= maxim_i2c_write(priv, M98088_REG_PWR_EN_OUT,
M98088_HPLEN | M98088_HPREN | M98088_SPLEN |
M98088_SPREN | M98088_DALEN | M98088_DAREN);
if (error < 0)
return -EIO;
return 0;
}
static int max98088_do_init(struct maxim_priv *priv, int sampling_rate,
int mclk_freq, int bits_per_sample)
{
int ret = 0;
ret = max98088_setup_interface(priv);
if (ret < 0) {
debug("%s: max98088 setup interface failed\n", __func__);
return ret;
}
ret = max98088_set_sysclk(priv, mclk_freq);
if (ret < 0) {
debug("%s: max98088 codec set sys clock failed\n", __func__);
return ret;
}
ret = max98088_hw_params(priv, sampling_rate, bits_per_sample);
if (ret == 0) {
ret = max98088_set_fmt(priv, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
}
return ret;
}
static int max98088_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample,
uint channels)
{
struct maxim_priv *priv = dev_get_priv(dev);
return max98088_do_init(priv, rate, mclk_freq, bits_per_sample);
}
static int max98088_probe(struct udevice *dev)
{
struct maxim_priv *priv = dev_get_priv(dev);
int ret;
priv->dev = dev;
ret = max98088_device_init(priv);
if (ret < 0) {
debug("%s: max98088 codec chip init failed\n", __func__);
return ret;
}
return 0;
}
static const struct audio_codec_ops max98088_ops = {
.set_params = max98088_set_params,
};
static const struct udevice_id max98088_ids[] = {
{ .compatible = "maxim,max98088" },
{ }
};
U_BOOT_DRIVER(max98088) = {
.name = "max98088",
.id = UCLASS_AUDIO_CODEC,
.of_match = max98088_ids,
.probe = max98088_probe,
.ops = &max98088_ops,
.priv_auto_alloc_size = sizeof(struct maxim_priv),
};
@@ -0,0 +1,192 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* max98088.h -- MAX98088 ALSA SoC Audio driver
*
* Copyright 2010 Maxim Integrated Products
*/
#ifndef _MAX98088_H
#define _MAX98088_H
/* MAX98088 Registers Definition */
#define M98088_REG_IRQ_STATUS 0x00
#define M98088_REG_MIC_STATUS 0x01
#define M98088_REG_JACK_STAUS 0x02
#define M98088_REG_BATTERY_VOLTAGE 0x03
#define M98088_REG_IRQ_ENABLE 0x0f
#define M98088_REG_SYS_CLK 0X10
#define M98088_REG_DAI1_CLKMODE 0x11
#define M98088_REG_DAI1_CLKCFG_HI 0x12
#define M98088_REG_DAI1_CLKCFG_LO 0x13
#define M98088_REG_DAI1_FORMAT 0x14
#define M98088_REG_DAI1_CLOCK 0x15
#define M98088_REG_DAI1_IOCFG 0x16
#define M98088_REG_DAI1_TDM 0X17
#define M98088_REG_DAI1_FILTERS 0x18
#define M98088_REG_DAI2_CLKMODE 0x19
#define M98088_REG_DAI2_CLKCFG_HI 0x1a
#define M98088_REG_DAI2_CLKCFG_LO 0x1b
#define M98088_REG_DAI2_FORMAT 0x1c
#define M98088_REG_DAI2_CLOCK 0x1d
#define M98088_REG_DAI2_IOCFG 0x1e
#define M98088_REG_DAI2_TDM 0X1f
#define M98088_REG_DAI2_FILTERS 0x20
#define M98088_REG_SRC 0X21
#define M98088_REG_MIX_DAC 0X22
#define M98088_REG_MIX_ADC_LEFT 0x23
#define M98088_REG_MIX_ADC_RIGHT 0x24
#define M98088_REG_MIX_HP_LEFT 0x25
#define M98088_REG_MIX_HP_RIGHT 0x26
#define M98088_REG_MIX_HP_CNTL 0x27
#define M98088_REG_MIX_REC_LEFT 0x28
#define M98088_REG_MIX_REC_RIGHT 0x29
#define M98088_REG_MIC_REC_CNTL 0x2a
#define M98088_REG_MIX_SPK_LEFT 0x2b
#define M98088_REG_MIX_SPK_RIGHT 0x2c
#define M98088_REG_MIX_SPK_CNTL 0x2d
#define M98088_REG_LVL_SIDETONE 0x2e
#define M98088_REG_LVL_DAI1_PLAY 0x2f
#define M98088_REG_LVL_DAI1_PLAY_EQ 0x30
#define M98088_REG_LVL_DAI2_PLAY 0x31
#define M98088_REG_LVL_DAI2_PLAY_EQ 0x32
#define M98088_REG_LVL_ADC_L 0X33
#define M98088_REG_LVL_ADC_R 0X34
#define M98088_REG_LVL_MIC1 0X35
#define M98088_REG_LVL_MIC2 0X36
#define M98088_REG_LVL_INA 0X37
#define M98088_REG_LVL_INB 0X38
#define M98088_REG_LVL_HP_L 0X39
#define M98088_REG_LVL_HP_R 0X3a
#define M98088_REG_LVL_REC_L 0X3b
#define M98088_REG_LVL_REC_R 0X3c
#define M98088_REG_LVL_SPK_L 0X3d
#define M98088_REG_LVL_SPK_R 0X3e
#define M98088_REG_MICAGC_CFG 0x3f
#define M98088_REG_MICAGC_THRESH 0x40
#define M98088_REG_SPKDHP 0X41
#define M98088_REG_SPKDHP_THRESH 0x42
#define M98088_REG_SPKALC_COMP 0x43
#define M98088_REG_PWRLMT_CFG 0x44
#define M98088_REG_PWRLMT_TIME 0x45
#define M98088_REG_THDLMT_CFG 0x46
#define M98088_REG_CFG_AUDIO_IN 0x47
#define M98088_REG_CFG_MIC 0X48
#define M98088_REG_CFG_LEVEL 0X49
#define M98088_REG_CFG_BYPASS 0x4a
#define M98088_REG_CFG_JACKDET 0x4b
#define M98088_REG_PWR_EN_IN 0X4c
#define M98088_REG_PWR_EN_OUT 0x4d
#define M98088_REG_BIAS_CNTL 0X4e
#define M98088_REG_DAC_BIAS1 0X4f
#define M98088_REG_DAC_BIAS2 0X50
#define M98088_REG_PWR_SYS 0X51
#define M98088_REG_DAI1_EQ_BASE 0x52
#define M98088_REG_DAI2_EQ_BASE 0x84
#define M98088_REG_DAI1_BIQUAD_BASE 0xb6
#define M98088_REG_DAI2_BIQUAD_BASE 0xc0
#define M98088_REG_REV_ID 0xff
#define M98088_REG_CNT (0xff + 1)
/* MAX98088 Registers Bit Fields */
/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */
#define M98088_CLKMODE_MASK 0xFF
/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
#define M98088_DAI_MAS BIT(7)
#define M98088_DAI_WCI BIT(6)
#define M98088_DAI_BCI BIT(5)
#define M98088_DAI_DLY BIT(4)
#define M98088_DAI_TDM BIT(2)
#define M98088_DAI_FSW BIT(1)
#define M98088_DAI_WS BIT(0)
/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
#define M98088_DAI_BSEL64 BIT(0)
#define M98088_DAI_OSR64 BIT(6)
/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
#define M98088_S1NORMAL BIT(6)
#define M98088_S2NORMAL (2 << 6)
#define M98088_SDATA (3 << 0)
/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
#define M98088_DAI_DHF BIT(3)
/* M98088_REG_22_MIX_DAC */
#define M98088_DAI1L_TO_DACL BIT(7)
#define M98088_DAI1R_TO_DACL BIT(6)
#define M98088_DAI2L_TO_DACL BIT(5)
#define M98088_DAI2R_TO_DACL BIT(4)
#define M98088_DAI1L_TO_DACR BIT(3)
#define M98088_DAI1R_TO_DACR BIT(2)
#define M98088_DAI2L_TO_DACR BIT(1)
#define M98088_DAI2R_TO_DACR BIT(0)
/* M98088_REG_2A_MIC_REC_CNTL */
#define M98088_REC_LINEMODE BIT(7)
#define M98088_REC_LINEMODE_MASK BIT(7)
/* M98088_REG_2D_MIX_SPK_CNTL */
#define M98088_MIX_SPKR_GAIN_MASK (3 << 2)
#define M98088_MIX_SPKR_GAIN_SHIFT 2
#define M98088_MIX_SPKL_GAIN_MASK (3 << 0)
#define M98088_MIX_SPKL_GAIN_SHIFT 0
/* M98088_REG_2F_LVL_DAI1_PLAY, M98088_REG_31_LVL_DAI2_PLAY */
#define M98088_DAI_MUTE BIT(7)
#define M98088_DAI_MUTE_MASK BIT(7)
#define M98088_DAI_VOICE_GAIN_MASK (3 << 4)
#define M98088_DAI_ATTENUATION_MASK (0xf << 0)
#define M98088_DAI_ATTENUATION_SHIFT 0
/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
#define M98088_MICPRE_MASK (3 << 5)
#define M98088_MICPRE_SHIFT 5
/* M98088_REG_3A_LVL_HP_R */
#define M98088_HP_MUTE BIT(7)
/* M98088_REG_3C_LVL_REC_R */
#define M98088_REC_MUTE BIT(7)
/* M98088_REG_3E_LVL_SPK_R */
#define M98088_SP_MUTE BIT(7)
/* M98088_REG_48_CFG_MIC */
#define M98088_EXTMIC_MASK (3 << 0)
#define M98088_DIGMIC_L BIT(5)
#define M98088_DIGMIC_R BIT(4)
/* M98088_REG_49_CFG_LEVEL */
#define M98088_VSEN BIT(6)
#define M98088_ZDEN BIT(5)
#define M98088_EQ2EN BIT(1)
#define M98088_EQ1EN BIT(0)
/* M98088_REG_4C_PWR_EN_IN */
#define M98088_INAEN BIT(7)
#define M98088_INBEN BIT(6)
#define M98088_MBEN BIT(3)
#define M98088_ADLEN BIT(1)
#define M98088_ADREN BIT(0)
/* M98088_REG_4D_PWR_EN_OUT */
#define M98088_HPLEN BIT(7)
#define M98088_HPREN BIT(6)
#define M98088_HPEN (BIT(7) | BIT(6))
#define M98088_SPLEN BIT(5)
#define M98088_SPREN BIT(4)
#define M98088_RECEN BIT(3)
#define M98088_DALEN BIT(1)
#define M98088_DAREN BIT(0)
/* M98088_REG_51_PWR_SYS */
#define M98088_SHDNRUN BIT(7)
#define M98088_PERFMODE BIT(3)
#define M98088_HPPLYBACK BIT(2)
#define M98088_PWRSV8K BIT(1)
#define M98088_PWRSV BIT(0)
#endif
@@ -0,0 +1,370 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* max98090.c -- MAX98090 ALSA SoC Audio driver
*
* Copyright 2011 Maxim Integrated Products
*/
#include <common.h>
#include <audio_codec.h>
#include <div64.h>
#include <dm.h>
#include <i2c.h>
#include <i2s.h>
#include <sound.h>
#include <asm/gpio.h>
#include "maxim_codec.h"
#include "max98090.h"
/*
* Sets hw params for max98090
*
* @priv: max98090 information pointer
* @rate: Sampling rate
* @bits_per_sample: Bits per sample
*
* @return -EIO for error, 0 for success.
*/
int max98090_hw_params(struct maxim_priv *priv, unsigned int rate,
unsigned int bits_per_sample)
{
int error;
unsigned char value;
switch (bits_per_sample) {
case 16:
maxim_i2c_read(priv, M98090_REG_INTERFACE_FORMAT, &value);
error = maxim_bic_or(priv, M98090_REG_INTERFACE_FORMAT,
M98090_WS_MASK, 0);
maxim_i2c_read(priv, M98090_REG_INTERFACE_FORMAT, &value);
break;
default:
debug("%s: Illegal bits per sample %d.\n",
__func__, bits_per_sample);
return -1;
}
/* Update filter mode */
if (rate < 240000)
error |= maxim_bic_or(priv, M98090_REG_FILTER_CONFIG,
M98090_MODE_MASK, 0);
else
error |= maxim_bic_or(priv, M98090_REG_FILTER_CONFIG,
M98090_MODE_MASK, M98090_MODE_MASK);
/* Update sample rate mode */
if (rate < 50000)
error |= maxim_bic_or(priv, M98090_REG_FILTER_CONFIG,
M98090_DHF_MASK, 0);
else
error |= maxim_bic_or(priv, M98090_REG_FILTER_CONFIG,
M98090_DHF_MASK, M98090_DHF_MASK);
if (error < 0) {
debug("%s: Error setting hardware params.\n", __func__);
return -EIO;
}
priv->rate = rate;
return 0;
}
/*
* Configures Audio interface system clock for the given frequency
*
* @priv: max98090 information
* @freq: Sampling frequency in Hz
*
* @return -EIO for error, 0 for success.
*/
int max98090_set_sysclk(struct maxim_priv *priv, unsigned int freq)
{
int error = 0;
/* Requested clock frequency is already setup */
if (freq == priv->sysclk)
return 0;
/* Setup clocks for slave mode, and using the PLL
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
* 0x02 (when master clk is 20MHz to 40MHz)..
* 0x03 (when master clk is 40MHz to 60MHz)..
*/
if (freq >= 10000000 && freq < 20000000) {
error = maxim_i2c_write(priv, M98090_REG_SYSTEM_CLOCK,
M98090_PSCLK_DIV1);
} else if (freq >= 20000000 && freq < 40000000) {
error = maxim_i2c_write(priv, M98090_REG_SYSTEM_CLOCK,
M98090_PSCLK_DIV2);
} else if (freq >= 40000000 && freq < 60000000) {
error = maxim_i2c_write(priv, M98090_REG_SYSTEM_CLOCK,
M98090_PSCLK_DIV4);
} else {
debug("%s: Invalid master clock frequency\n", __func__);
return -1;
}
debug("%s: Clock at %uHz\n", __func__, freq);
if (error < 0)
return -1;
priv->sysclk = freq;
return 0;
}
/*
* Sets Max98090 I2S format
*
* @priv: max98090 information
* @fmt: i2S format - supports a subset of the options defined in i2s.h.
*
* @return -EIO for error, 0 for success.
*/
int max98090_set_fmt(struct maxim_priv *priv, int fmt)
{
u8 regval = 0;
int error = 0;
if (fmt == priv->fmt)
return 0;
priv->fmt = fmt;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* Set to slave mode PLL - MAS mode off */
error |= maxim_i2c_write(priv, M98090_REG_CLOCK_RATIO_NI_MSB,
0x00);
error |= maxim_i2c_write(priv, M98090_REG_CLOCK_RATIO_NI_LSB,
0x00);
error |= maxim_bic_or(priv, M98090_REG_CLOCK_MODE,
M98090_USE_M1_MASK, 0);
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* Set to master mode */
debug("Master mode not supported\n");
break;
case SND_SOC_DAIFMT_CBS_CFM:
case SND_SOC_DAIFMT_CBM_CFS:
default:
debug("%s: Clock mode unsupported\n", __func__);
return -EINVAL;
}
error |= maxim_i2c_write(priv, M98090_REG_MASTER_MODE, regval);
regval = 0;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
regval |= M98090_DLY_MASK;
break;
case SND_SOC_DAIFMT_LEFT_J:
break;
case SND_SOC_DAIFMT_RIGHT_J:
regval |= M98090_RJ_MASK;
break;
case SND_SOC_DAIFMT_DSP_A:
/* Not supported mode */
default:
debug("%s: Unrecognized format.\n", __func__);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
regval |= M98090_WCI_MASK;
break;
case SND_SOC_DAIFMT_IB_NF:
regval |= M98090_BCI_MASK;
break;
case SND_SOC_DAIFMT_IB_IF:
regval |= M98090_BCI_MASK | M98090_WCI_MASK;
break;
default:
debug("%s: Unrecognized inversion settings.\n", __func__);
return -EINVAL;
}
error |= maxim_i2c_write(priv, M98090_REG_INTERFACE_FORMAT, regval);
if (error < 0) {
debug("%s: Error setting i2s format.\n", __func__);
return -EIO;
}
return 0;
}
/*
* resets the audio codec
*
* @priv: max98090 information
* @return -EIO for error, 0 for success.
*/
static int max98090_reset(struct maxim_priv *priv)
{
int ret;
/*
* Gracefully reset the DSP core and the codec hardware in a proper
* sequence.
*/
ret = maxim_i2c_write(priv, M98090_REG_SOFTWARE_RESET,
M98090_SWRESET_MASK);
if (ret != 0) {
debug("%s: Failed to reset DSP: %d\n", __func__, ret);
return ret;
}
mdelay(20);
return 0;
}
/*
* Initialise max98090 codec device
*
* @priv: max98090 information
*
* @return -EIO for error, 0 for success.
*/
int max98090_device_init(struct maxim_priv *priv)
{
unsigned char id;
int error = 0;
/* reset the codec, the DSP core, and disable all interrupts */
error = max98090_reset(priv);
if (error != 0) {
debug("Reset\n");
return error;
}
/* initialize private data */
priv->sysclk = -1U;
priv->rate = -1U;
priv->fmt = -1U;
error = maxim_i2c_read(priv, M98090_REG_REVISION_ID, &id);
if (error < 0) {
debug("%s: Failure reading hardware revision: %d\n",
__func__, id);
return -EIO;
}
debug("%s: Hardware revision: %d\n", __func__, id);
return 0;
}
static int max98090_setup_interface(struct maxim_priv *priv)
{
unsigned char id;
int error;
/* Reading interrupt status to clear them */
error = maxim_i2c_read(priv, M98090_REG_DEVICE_STATUS, &id);
error |= maxim_i2c_write(priv, M98090_REG_DAC_CONTROL,
M98090_DACHP_MASK);
error |= maxim_i2c_write(priv, M98090_REG_BIAS_CONTROL,
M98090_VCM_MODE_MASK);
error |= maxim_i2c_write(priv, M98090_REG_LEFT_SPK_MIXER, 0x1);
error |= maxim_i2c_write(priv, M98090_REG_RIGHT_SPK_MIXER, 0x2);
error |= maxim_i2c_write(priv, M98090_REG_LEFT_SPK_VOLUME, 0x25);
error |= maxim_i2c_write(priv, M98090_REG_RIGHT_SPK_VOLUME, 0x25);
error |= maxim_i2c_write(priv, M98090_REG_CLOCK_RATIO_NI_MSB, 0x0);
error |= maxim_i2c_write(priv, M98090_REG_CLOCK_RATIO_NI_LSB, 0x0);
error |= maxim_i2c_write(priv, M98090_REG_MASTER_MODE, 0x0);
error |= maxim_i2c_write(priv, M98090_REG_INTERFACE_FORMAT, 0x0);
error |= maxim_i2c_write(priv, M98090_REG_IO_CONFIGURATION,
M98090_SDIEN_MASK);
error |= maxim_i2c_write(priv, M98090_REG_DEVICE_SHUTDOWN,
M98090_SHDNN_MASK);
error |= maxim_i2c_write(priv, M98090_REG_OUTPUT_ENABLE,
M98090_HPREN_MASK | M98090_HPLEN_MASK |
M98090_SPREN_MASK | M98090_SPLEN_MASK |
M98090_DAREN_MASK | M98090_DALEN_MASK);
error |= maxim_i2c_write(priv, M98090_REG_IO_CONFIGURATION,
M98090_SDOEN_MASK | M98090_SDIEN_MASK);
if (error < 0)
return -EIO;
return 0;
}
static int max98090_do_init(struct maxim_priv *priv, int sampling_rate,
int mclk_freq, int bits_per_sample)
{
int ret = 0;
ret = max98090_setup_interface(priv);
if (ret < 0) {
debug("%s: max98090 setup interface failed\n", __func__);
return ret;
}
ret = max98090_set_sysclk(priv, mclk_freq);
if (ret < 0) {
debug("%s: max98090 codec set sys clock failed\n", __func__);
return ret;
}
ret = max98090_hw_params(priv, sampling_rate, bits_per_sample);
if (ret == 0) {
ret = max98090_set_fmt(priv, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
}
return ret;
}
static int max98090_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample,
uint channels)
{
struct maxim_priv *priv = dev_get_priv(dev);
return max98090_do_init(priv, rate, mclk_freq, bits_per_sample);
}
static int max98090_probe(struct udevice *dev)
{
struct maxim_priv *priv = dev_get_priv(dev);
int ret;
priv->dev = dev;
ret = max98090_device_init(priv);
if (ret < 0) {
debug("%s: max98090 codec chip init failed\n", __func__);
return ret;
}
return 0;
}
static const struct audio_codec_ops max98090_ops = {
.set_params = max98090_set_params,
};
static const struct udevice_id max98090_ids[] = {
{ .compatible = "maxim,max98090" },
{ }
};
U_BOOT_DRIVER(max98090) = {
.name = "max98090",
.id = UCLASS_AUDIO_CODEC,
.of_match = max98090_ids,
.probe = max98090_probe,
.ops = &max98090_ops,
.priv_auto_alloc_size = sizeof(struct maxim_priv),
};
@@ -0,0 +1,663 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* max98090.h -- MAX98090 ALSA SoC Audio driver
*
* Copyright 2011 Maxim Integrated Products
*/
#ifndef _MAX98090_H
#define _MAX98090_H
#include "maxim_codec.h"
/* MAX98090 Registers Definition */
#define M98090_REG_SOFTWARE_RESET 0x00
#define M98090_REG_DEVICE_STATUS 0x01
#define M98090_REG_QUICK_SAMPLE_RATE 0x05
#define M98090_REG_DAI_INTERFACE 0x06
#define M98090_REG_DAC_PATH 0x07
#define M98090_REG_MIC_BIAS_VOLTAGE 0x12
#define M98090_REG_DIGITAL_MIC_ENABLE 0x13
#define M98090_REG_DIGITAL_MIC_CONFIG 0x14
#define M98090_REG_SYSTEM_CLOCK 0x1B
#define M98090_REG_CLOCK_RATIO_NI_MSB 0x1D
#define M98090_REG_CLOCK_MODE 0x1C
#define M98090_REG_CLOCK_RATIO_NI_LSB 0x1E
#define M98090_REG_MASTER_MODE 0x21
#define M98090_REG_INTERFACE_FORMAT 0x22
#define M98090_REG_IO_CONFIGURATION 0x25
#define M98090_REG_FILTER_CONFIG 0x26
#define M98090_REG_LEFT_HP_MIXER 0x29
#define M98090_REG_RIGHT_HP_MIXER 0x2a
#define M98090_REG_HP_CONTROL 0x2b
#define M98090_REG_LEFT_HP_VOLUME 0x2c
#define M98090_REG_RIGHT_HP_VOLUME 0x2d
#define M98090_REG_LEFT_SPK_MIXER 0x2e
#define M98090_REG_RIGHT_SPK_MIXER 0x2f
#define M98090_REG_SPK_CONTROL 0x30
#define M98090_REG_LEFT_SPK_VOLUME 0x31
#define M98090_REG_RIGHT_SPK_VOLUME 0x32
#define M98090_REG_RCV_LOUTL_CONTROL 0x38
#define M98090_REG_RCV_LOUTL_VOLUME 0x39
#define M98090_REG_LOUTR_MIXER 0x3a
#define M98090_REG_LOUTR_CONTROL 0x3b
#define M98090_REG_LOUTR_VOLUME 0x3c
#define M98090_REG_JACK_DETECT 0x3d
#define M98090_REG_INPUT_ENABLE 0x3e
#define M98090_REG_OUTPUT_ENABLE 0x3f
#define M98090_REG_LEVEL_CONTROL 0x40
#define M98090_REG_DSP_FILTER_ENABLE 0x41
#define M98090_REG_BIAS_CONTROL 0x42
#define M98090_REG_DAC_CONTROL 0x43
#define M98090_REG_ADC_CONTROL 0x44
#define M98090_REG_DEVICE_SHUTDOWN 0x45
#define M98090_REG_REVISION_ID 0xff
#define M98090_REG_CNT (0xff + 1)
#define M98090_REG_MAX_CACHed 0x45
/* MAX98090 Registers Bit Fields */
/*
* M98090_REG_SOFTWARE_RESET 0x00
*/
#define M98090_SWRESET_MASK BIT(7)
/*
* M98090_REG_QUICK_SAMPLE_RATE 0x05
*/
#define M98090_SR_96K_MASK BIT(5)
#define M98090_SR_96K_SHIFT 5
#define M98090_SR_96K_WIDTH 1
#define M98090_SR_32K_MASK BIT(4)
#define M98090_SR_32K_SHIFT 4
#define M98090_SR_32K_WIDTH 1
#define M98090_SR_48K_MASK BIT(3)
#define M98090_SR_48K_SHIFT 3
#define M98090_SR_48K_WIDTH 1
#define M98090_SR_44K1_MASK BIT(2)
#define M98090_SR_44K1_SHIFT 2
#define M98090_SR_44K1_WIDTH 1
#define M98090_SR_16K_MASK BIT(1)
#define M98090_SR_16K_SHIFT 1
#define M98090_SR_16K_WIDTH 1
#define M98090_SR_8K_MASK BIT(0)
#define M98090_SR_8K_SHIFT 0
#define M98090_SR_8K_WIDTH 1
#define M98090_SR_MASK 0x3F
#define M98090_SR_ALL_SHIFT 0
#define M98090_SR_ALL_WIDTH 8
#define M98090_SR_ALL_NUM BIT(M98090_SR_ALL_WIDTH)
/*
* M98090_REG_DAI_INTERFACE 0x06
*/
#define M98090_RJ_M_MASK BIT(5)
#define M98090_RJ_M_SHIFT 5
#define M98090_RJ_M_WIDTH 1
#define M98090_RJ_S_MASK BIT(4)
#define M98090_RJ_S_SHIFT 4
#define M98090_RJ_S_WIDTH 1
#define M98090_LJ_M_MASK BIT(3)
#define M98090_LJ_M_SHIFT 3
#define M98090_LJ_M_WIDTH 1
#define M98090_LJ_S_MASK BIT(2)
#define M98090_LJ_S_SHIFT 2
#define M98090_LJ_S_WIDTH 1
#define M98090_I2S_M_MASK BIT(1)
#define M98090_I2S_M_SHIFT 1
#define M98090_I2S_M_WIDTH 1
#define M98090_I2S_S_MASK BIT(0)
#define M98090_I2S_S_SHIFT 0
#define M98090_I2S_S_WIDTH 1
#define M98090_DAI_ALL_SHIFT 0
#define M98090_DAI_ALL_WIDTH 8
#define M98090_DAI_ALL_NUM BIT(M98090_DAI_ALL_WIDTH)
/*
* M98090_REG_DAC_PATH 0x07
*/
#define M98090_DIG2_HP_MASK BIT(7)
#define M98090_DIG2_HP_SHIFT 7
#define M98090_DIG2_HP_WIDTH 1
#define M98090_DIG2_EAR_MASK BIT(6)
#define M98090_DIG2_EAR_SHIFT 6
#define M98090_DIG2_EAR_WIDTH 1
#define M98090_DIG2_SPK_MASK BIT(5)
#define M98090_DIG2_SPK_SHIFT 5
#define M98090_DIG2_SPK_WIDTH 1
#define M98090_DIG2_LOUT_MASK BIT(4)
#define M98090_DIG2_LOUT_SHIFT 4
#define M98090_DIG2_LOUT_WIDTH 1
#define M98090_DIG2_ALL_SHIFT 0
#define M98090_DIG2_ALL_WIDTH 8
#define M98090_DIG2_ALL_NUM BIT(M98090_DIG2_ALL_WIDTH)
/*
* M98090_REG_MIC_BIAS_VOLTAGE 0x12
*/
#define M98090_MBVSEL_MASK (3 << 0)
#define M98090_MBVSEL_SHIFT 0
#define M98090_MBVSEL_WIDTH 2
#define M98090_MBVSEL_2V8 (3 << 0)
#define M98090_MBVSEL_2V55 (2 << 0)
#define M98090_MBVSEL_2V4 BIT(0)
#define M98090_MBVSEL_2V2 (0 << 0)
/*
* M98090_REG_DIGITAL_MIC_ENABLE 0x13
*/
#define M98090_MICCLK_MASK (7 << 4)
#define M98090_MICCLK_SHIFT 4
#define M98090_MICCLK_WIDTH 3
#define M98090_DIGMIC4_MASK BIT(3)
#define M98090_DIGMIC4_SHIFT 3
#define M98090_DIGMIC4_WIDTH 1
#define M98090_DIGMIC4_NUM BIT(M98090_DIGMIC4_WIDTH)
#define M98090_DIGMIC3_MASK BIT(2)
#define M98090_DIGMIC3_SHIFT 2
#define M98090_DIGMIC3_WIDTH 1
#define M98090_DIGMIC3_NUM BIT(M98090_DIGMIC3_WIDTH)
#define M98090_DIGMICR_MASK BIT(1)
#define M98090_DIGMICR_SHIFT 1
#define M98090_DIGMICR_WIDTH 1
#define M98090_DIGMICR_NUM BIT(M98090_DIGMICR_WIDTH)
#define M98090_DIGMICL_MASK BIT(0)
#define M98090_DIGMICL_SHIFT 0
#define M98090_DIGMICL_WIDTH 1
#define M98090_DIGMICL_NUM BIT(M98090_DIGMICL_WIDTH)
/*
* M98090_REG_DIGITAL_MIC_CONFIG 0x14
*/
#define M98090_DMIC_COMP_MASK (15 << 4)
#define M98090_DMIC_COMP_SHIFT 4
#define M98090_DMIC_COMP_WIDTH 4
#define M98090_DMIC_COMP_NUM BIT(M98090_DMIC_COMP_WIDTH)
#define M98090_DMIC_FREQ_MASK (3 << 0)
#define M98090_DMIC_FREQ_SHIFT 0
#define M98090_DMIC_FREQ_WIDTH 2
/*
* M98090_REG_CLOCK_MODE 0x1B
*/
#define M98090_PSCLK_MASK (3 << 4)
#define M98090_PSCLK_SHIFT 4
#define M98090_PSCLK_WIDTH 2
#define M98090_PSCLK_DISABLED (0 << 4)
#define M98090_PSCLK_DIV1 BIT(4)
#define M98090_PSCLK_DIV2 (2 << 4)
#define M98090_PSCLK_DIV4 (3 << 4)
/*
* M98090_REG_INTERFACE_FORMAT 0x22
*/
#define M98090_RJ_MASK BIT(5)
#define M98090_RJ_SHIFT 5
#define M98090_RJ_WIDTH 1
#define M98090_WCI_MASK BIT(4)
#define M98090_WCI_SHIFT 4
#define M98090_WCI_WIDTH 1
#define M98090_BCI_MASK BIT(3)
#define M98090_BCI_SHIFT 3
#define M98090_BCI_WIDTH 1
#define M98090_DLY_MASK BIT(2)
#define M98090_DLY_SHIFT 2
#define M98090_DLY_WIDTH 1
#define M98090_WS_MASK (3 << 0)
#define M98090_WS_SHIFT 0
#define M98090_WS_WIDTH 2
#define M98090_WS_NUM BIT(M98090_WS_WIDTH)
/* M98090_REG_IO_CONFIGURATION 0x25 */
#define M98090_LTEN_MASK BIT(5)
#define M98090_LTEN_SHIFT 5
#define M98090_LTEN_WIDTH 1
#define M98090_LTEN_NUM BIT(M98090_LTEN_WIDTH)
#define M98090_LBEN_MASK BIT(4)
#define M98090_LBEN_SHIFT 4
#define M98090_LBEN_WIDTH 1
#define M98090_LBEN_NUM BIT(M98090_LBEN_WIDTH)
#define M98090_DMONO_MASK BIT(3)
#define M98090_DMONO_SHIFT 3
#define M98090_DMONO_WIDTH 1
#define M98090_DMONO_NUM BIT(M98090_DMONO_WIDTH)
#define M98090_HIZOFF_MASK BIT(2)
#define M98090_HIZOFF_SHIFT 2
#define M98090_HIZOFF_WIDTH 1
#define M98090_HIZOFF_NUM BIT(M98090_HIZOFF_WIDTH)
#define M98090_SDOEN_MASK BIT(1)
#define M98090_SDOEN_SHIFT 1
#define M98090_SDOEN_WIDTH 1
#define M98090_SDOEN_NUM BIT(M98090_SDOEN_WIDTH)
#define M98090_SDIEN_MASK BIT(0)
#define M98090_SDIEN_SHIFT 0
#define M98090_SDIEN_WIDTH 1
#define M98090_SDIEN_NUM BIT(M98090_SDIEN_WIDTH)
/*
* M98090_REG_FILTER_CONFIG 0x26
*/
#define M98090_MODE_MASK BIT(7)
#define M98090_MODE_SHIFT 7
#define M98090_MODE_WIDTH 1
#define M98090_AHPF_MASK BIT(6)
#define M98090_AHPF_SHIFT 6
#define M98090_AHPF_WIDTH 1
#define M98090_AHPF_NUM BIT(M98090_AHPF_WIDTH)
#define M98090_DHPF_MASK BIT(5)
#define M98090_DHPF_SHIFT 5
#define M98090_DHPF_WIDTH 1
#define M98090_DHPF_NUM BIT(M98090_DHPF_WIDTH)
#define M98090_DHF_MASK BIT(4)
#define M98090_DHF_SHIFT 4
#define M98090_DHF_WIDTH 1
#define M98090_FLT_DMIC34MODE_MASK BIT(3)
#define M98090_FLT_DMIC34MODE_SHIFT 3
#define M98090_FLT_DMIC34MODE_WIDTH 1
#define M98090_FLT_DMIC34HPF_MASK BIT(2)
#define M98090_FLT_DMIC34HPF_SHIFT 2
#define M98090_FLT_DMIC34HPF_WIDTH 1
#define M98090_FLT_DMIC34HPF_NUM BIT(M98090_FLT_DMIC34HPF_WIDTH)
/*
* M98090_REG_CLOCK_MODE
*/
#define M98090_FREQ_MASK (15 << 4)
#define M98090_FREQ_SHIFT 4
#define M98090_FREQ_WIDTH 4
#define M98090_USE_M1_MASK BIT(0)
#define M98090_USE_M1_SHIFT 0
#define M98090_USE_M1_WIDTH 1
#define M98090_USE_M1_NUM BIT(M98090_USE_M1_WIDTH)
/*
* M98090_REG_LEFT_HP_MIXER 0x29
*/
#define M98090_MIXHPL_MIC2_MASK BIT(5)
#define M98090_MIXHPL_MIC2_SHIFT 5
#define M98090_MIXHPL_MIC2_WIDTH 1
#define M98090_MIXHPL_MIC1_MASK BIT(4)
#define M98090_MIXHPL_MIC1_SHIFT 4
#define M98090_MIXHPL_MIC1_WIDTH 1
#define M98090_MIXHPL_LINEB_MASK BIT(3)
#define M98090_MIXHPL_LINEB_SHIFT 3
#define M98090_MIXHPL_LINEB_WIDTH 1
#define M98090_MIXHPL_LINEA_MASK BIT(2)
#define M98090_MIXHPL_LINEA_SHIFT 2
#define M98090_MIXHPL_LINEA_WIDTH 1
#define M98090_MIXHPL_DACR_MASK BIT(1)
#define M98090_MIXHPL_DACR_SHIFT 1
#define M98090_MIXHPL_DACR_WIDTH 1
#define M98090_MIXHPL_DACL_MASK BIT(0)
#define M98090_MIXHPL_DACL_SHIFT 0
#define M98090_MIXHPL_DACL_WIDTH 1
#define M98090_MIXHPL_MASK (63 << 0)
#define M98090_MIXHPL_SHIFT 0
#define M98090_MIXHPL_WIDTH 6
/*
* M98090_REG_RIGHT_HP_MIXER 0x2A
*/
#define M98090_MIXHPR_MIC2_MASK BIT(5)
#define M98090_MIXHPR_MIC2_SHIFT 5
#define M98090_MIXHPR_MIC2_WIDTH 1
#define M98090_MIXHPR_MIC1_MASK BIT(4)
#define M98090_MIXHPR_MIC1_SHIFT 4
#define M98090_MIXHPR_MIC1_WIDTH 1
#define M98090_MIXHPR_LINEB_MASK BIT(3)
#define M98090_MIXHPR_LINEB_SHIFT 3
#define M98090_MIXHPR_LINEB_WIDTH 1
#define M98090_MIXHPR_LINEA_MASK BIT(2)
#define M98090_MIXHPR_LINEA_SHIFT 2
#define M98090_MIXHPR_LINEA_WIDTH 1
#define M98090_MIXHPR_DACR_MASK BIT(1)
#define M98090_MIXHPR_DACR_SHIFT 1
#define M98090_MIXHPR_DACR_WIDTH 1
#define M98090_MIXHPR_DACL_MASK BIT(0)
#define M98090_MIXHPR_DACL_SHIFT 0
#define M98090_MIXHPR_DACL_WIDTH 1
#define M98090_MIXHPR_MASK (63 << 0)
#define M98090_MIXHPR_SHIFT 0
#define M98090_MIXHPR_WIDTH 6
/*
* M98090_REG_LEFT_HP_VOLUME 0x2C
*/
#define M98090_HPLM_MASK BIT(7)
#define M98090_HPLM_SHIFT 7
#define M98090_HPLM_WIDTH 1
#define M98090_HPVOLL_MASK (31 << 0)
#define M98090_HPVOLL_SHIFT 0
#define M98090_HPVOLL_WIDTH 5
#define M98090_HPVOLL_NUM BIT(M98090_HPVOLL_WIDTH)
/*
* M98090_REG_RIGHT_HP_VOLUME 0x2D
*/
#define M98090_HPRM_MASK BIT(7)
#define M98090_HPRM_SHIFT 7
#define M98090_HPRM_WIDTH 1
#define M98090_HPVOLR_MASK (31 << 0)
#define M98090_HPVOLR_SHIFT 0
#define M98090_HPVOLR_WIDTH 5
#define M98090_HPVOLR_NUM BIT(M98090_HPVOLR_WIDTH)
/*
* M98090_REG_LEFT_SPK_MIXER 0x2E
*/
#define M98090_MIXSPL_MIC2_MASK BIT(5)
#define M98090_MIXSPL_MIC2_SHIFT 5
#define M98090_MIXSPL_MIC2_WIDTH 1
#define M98090_MIXSPL_MIC1_MASK BIT(4)
#define M98090_MIXSPL_MIC1_SHIFT 4
#define M98090_MIXSPL_MIC1_WIDTH 1
#define M98090_MIXSPL_LINEB_MASK BIT(3)
#define M98090_MIXSPL_LINEB_SHIFT 3
#define M98090_MIXSPL_LINEB_WIDTH 1
#define M98090_MIXSPL_LINEA_MASK BIT(2)
#define M98090_MIXSPL_LINEA_SHIFT 2
#define M98090_MIXSPL_LINEA_WIDTH 1
#define M98090_MIXSPL_DACR_MASK BIT(1)
#define M98090_MIXSPL_DACR_SHIFT 1
#define M98090_MIXSPL_DACR_WIDTH 1
#define M98090_MIXSPL_DACL_MASK BIT(0)
#define M98090_MIXSPL_DACL_SHIFT 0
#define M98090_MIXSPL_DACL_WIDTH 1
#define M98090_MIXSPL_MASK (63 << 0)
#define M98090_MIXSPL_SHIFT 0
#define M98090_MIXSPL_WIDTH 6
#define M98090_MIXSPR_DACR_MASK BIT(1)
#define M98090_MIXSPR_DACR_SHIFT 1
#define M98090_MIXSPR_DACR_WIDTH 1
/*
* M98090_REG_RIGHT_SPK_MIXER 0x2F
*/
#define M98090_SPK_SLAVE_MASK BIT(6)
#define M98090_SPK_SLAVE_SHIFT 6
#define M98090_SPK_SLAVE_WIDTH 1
#define M98090_MIXSPR_MIC2_MASK BIT(5)
#define M98090_MIXSPR_MIC2_SHIFT 5
#define M98090_MIXSPR_MIC2_WIDTH 1
#define M98090_MIXSPR_MIC1_MASK BIT(4)
#define M98090_MIXSPR_MIC1_SHIFT 4
#define M98090_MIXSPR_MIC1_WIDTH 1
#define M98090_MIXSPR_LINEB_MASK BIT(3)
#define M98090_MIXSPR_LINEB_SHIFT 3
#define M98090_MIXSPR_LINEB_WIDTH 1
#define M98090_MIXSPR_LINEA_MASK BIT(2)
#define M98090_MIXSPR_LINEA_SHIFT 2
#define M98090_MIXSPR_LINEA_WIDTH 1
#define M98090_MIXSPR_DACR_MASK BIT(1)
#define M98090_MIXSPR_DACR_SHIFT 1
#define M98090_MIXSPR_DACR_WIDTH 1
#define M98090_MIXSPR_DACL_MASK BIT(0)
#define M98090_MIXSPR_DACL_SHIFT 0
#define M98090_MIXSPR_DACL_WIDTH 1
#define M98090_MIXSPR_MASK (63 << 0)
#define M98090_MIXSPR_SHIFT 0
#define M98090_MIXSPR_WIDTH 6
/*
* M98090_REG_LEFT_SPK_VOLUME 0x31
*/
#define M98090_SPLM_MASK BIT(7)
#define M98090_SPLM_SHIFT 7
#define M98090_SPLM_WIDTH 1
#define M98090_SPVOLL_MASK (63 << 0)
#define M98090_SPVOLL_SHIFT 0
#define M98090_SPVOLL_WIDTH 6
#define M98090_SPVOLL_NUM 40
/*
* M98090_REG_RIGHT_SPK_VOLUME 0x32
*/
#define M98090_SPRM_MASK BIT(7)
#define M98090_SPRM_SHIFT 7
#define M98090_SPRM_WIDTH 1
#define M98090_SPVOLR_MASK (63 << 0)
#define M98090_SPVOLR_SHIFT 0
#define M98090_SPVOLR_WIDTH 6
#define M98090_SPVOLR_NUM 40
/*
* M98090_REG_RCV_LOUTL_MIXER 0x37
*/
#define M98090_MIXRCVL_MIC2_MASK BIT(5)
#define M98090_MIXRCVL_MIC2_SHIFT 5
#define M98090_MIXRCVL_MIC2_WIDTH 1
#define M98090_MIXRCVL_MIC1_MASK BIT(4)
#define M98090_MIXRCVL_MIC1_SHIFT 4
#define M98090_MIXRCVL_MIC1_WIDTH 1
#define M98090_MIXRCVL_LINEB_MASK BIT(3)
#define M98090_MIXRCVL_LINEB_SHIFT 3
#define M98090_MIXRCVL_LINEB_WIDTH 1
#define M98090_MIXRCVL_LINEA_MASK BIT(2)
#define M98090_MIXRCVL_LINEA_SHIFT 2
#define M98090_MIXRCVL_LINEA_WIDTH 1
#define M98090_MIXRCVL_DACR_MASK BIT(1)
#define M98090_MIXRCVL_DACR_SHIFT 1
#define M98090_MIXRCVL_DACR_WIDTH 1
#define M98090_MIXRCVL_DACL_MASK BIT(0)
#define M98090_MIXRCVL_DACL_SHIFT 0
#define M98090_MIXRCVL_DACL_WIDTH 1
#define M98090_MIXRCVL_MASK (63 << 0)
#define M98090_MIXRCVL_SHIFT 0
#define M98090_MIXRCVL_WIDTH 6
/*
* M98090_REG_RCV_LOUTL_CONTROL 0x38
*/
#define M98090_MIXRCVLG_MASK (3 << 0)
#define M98090_MIXRCVLG_SHIFT 0
#define M98090_MIXRCVLG_WIDTH 2
#define M98090_MIXRCVLG_NUM BIT(M98090_MIXRCVLG_WIDTH)
/*
* M98090_REG_RCV_LOUTL_VOLUME 0x39
*/
#define M98090_RCVLM_MASK BIT(7)
#define M98090_RCVLM_SHIFT 7
#define M98090_RCVLM_WIDTH 1
#define M98090_RCVLVOL_MASK (31 << 0)
#define M98090_RCVLVOL_SHIFT 0
#define M98090_RCVLVOL_WIDTH 5
#define M98090_RCVLVOL_NUM BIT(M98090_RCVLVOL_WIDTH)
/*
* M98090_REG_LOUTR_MIXER 0x3A
*/
#define M98090_LINMOD_MASK BIT(7)
#define M98090_LINMOD_SHIFT 7
#define M98090_LINMOD_WIDTH 1
#define M98090_MIXRCVR_MIC2_MASK BIT(5)
#define M98090_MIXRCVR_MIC2_SHIFT 5
#define M98090_MIXRCVR_MIC2_WIDTH 1
#define M98090_MIXRCVR_MIC1_MASK BIT(4)
#define M98090_MIXRCVR_MIC1_SHIFT 4
#define M98090_MIXRCVR_MIC1_WIDTH 1
#define M98090_MIXRCVR_LINEB_MASK BIT(3)
#define M98090_MIXRCVR_LINEB_SHIFT 3
#define M98090_MIXRCVR_LINEB_WIDTH 1
#define M98090_MIXRCVR_LINEA_MASK BIT(2)
#define M98090_MIXRCVR_LINEA_SHIFT 2
#define M98090_MIXRCVR_LINEA_WIDTH 1
#define M98090_MIXRCVR_DACR_MASK BIT(1)
#define M98090_MIXRCVR_DACR_SHIFT 1
#define M98090_MIXRCVR_DACR_WIDTH 1
#define M98090_MIXRCVR_DACL_MASK BIT(0)
#define M98090_MIXRCVR_DACL_SHIFT 0
#define M98090_MIXRCVR_DACL_WIDTH 1
#define M98090_MIXRCVR_MASK (63 << 0)
#define M98090_MIXRCVR_SHIFT 0
#define M98090_MIXRCVR_WIDTH 6
/*
* M98090_REG_LOUTR_VOLUME 0x3C
*/
#define M98090_RCVRM_MASK BIT(7)
#define M98090_RCVRM_SHIFT 7
#define M98090_RCVRM_WIDTH 1
#define M98090_RCVRVOL_MASK (31 << 0)
#define M98090_RCVRVOL_SHIFT 0
#define M98090_RCVRVOL_WIDTH 5
#define M98090_RCVRVOL_NUM BIT(M98090_RCVRVOL_WIDTH)
/*
* M98090_REG_JACK_DETECT 0x3D
*/
#define M98090_JDETEN_MASK BIT(7)
#define M98090_JDETEN_SHIFT 7
#define M98090_JDETEN_WIDTH 1
#define M98090_JDWK_MASK BIT(6)
#define M98090_JDWK_SHIFT 6
#define M98090_JDWK_WIDTH 1
#define M98090_JDEB_MASK (3 << 0)
#define M98090_JDEB_SHIFT 0
#define M98090_JDEB_WIDTH 2
#define M98090_JDEB_25MS (0 << 0)
#define M98090_JDEB_50MS BIT(0)
#define M98090_JDEB_100MS (2 << 0)
#define M98090_JDEB_200MS (3 << 0)
/*
* M98090_REG_INPUT_ENABLE 0x3E
*/
#define M98090_MBEN_MASK BIT(4)
#define M98090_MBEN_SHIFT 4
#define M98090_MBEN_WIDTH 1
#define M98090_LINEAEN_MASK BIT(3)
#define M98090_LINEAEN_SHIFT 3
#define M98090_LINEAEN_WIDTH 1
#define M98090_LINEBEN_MASK BIT(2)
#define M98090_LINEBEN_SHIFT 2
#define M98090_LINEBEN_WIDTH 1
#define M98090_ADREN_MASK BIT(1)
#define M98090_ADREN_SHIFT 1
#define M98090_ADREN_WIDTH 1
#define M98090_ADLEN_MASK BIT(0)
#define M98090_ADLEN_SHIFT 0
#define M98090_ADLEN_WIDTH 1
/*
* M98090_REG_OUTPUT_ENABLE 0x3F
*/
#define M98090_HPREN_MASK BIT(7)
#define M98090_HPREN_SHIFT 7
#define M98090_HPREN_WIDTH 1
#define M98090_HPLEN_MASK BIT(6)
#define M98090_HPLEN_SHIFT 6
#define M98090_HPLEN_WIDTH 1
#define M98090_SPREN_MASK BIT(5)
#define M98090_SPREN_SHIFT 5
#define M98090_SPREN_WIDTH 1
#define M98090_SPLEN_MASK BIT(4)
#define M98090_SPLEN_SHIFT 4
#define M98090_SPLEN_WIDTH 1
#define M98090_RCVLEN_MASK BIT(3)
#define M98090_RCVLEN_SHIFT 3
#define M98090_RCVLEN_WIDTH 1
#define M98090_RCVREN_MASK BIT(2)
#define M98090_RCVREN_SHIFT 2
#define M98090_RCVREN_WIDTH 1
#define M98090_DAREN_MASK BIT(1)
#define M98090_DAREN_SHIFT 1
#define M98090_DAREN_WIDTH 1
#define M98090_DALEN_MASK BIT(0)
#define M98090_DALEN_SHIFT 0
#define M98090_DALEN_WIDTH 1
/*
* M98090_REG_LEVEL_CONTROL 0x40
*/
#define M98090_ZDENN_MASK BIT(2)
#define M98090_ZDENN_SHIFT 2
#define M98090_ZDENN_WIDTH 1
#define M98090_ZDENN_NUM BIT(M98090_ZDENN_WIDTH)
#define M98090_VS2ENN_MASK BIT(1)
#define M98090_VS2ENN_SHIFT 1
#define M98090_VS2ENN_WIDTH 1
#define M98090_VS2ENN_NUM BIT(M98090_VS2ENN_WIDTH)
#define M98090_VSENN_MASK BIT(0)
#define M98090_VSENN_SHIFT 0
#define M98090_VSENN_WIDTH 1
#define M98090_VSENN_NUM BIT(M98090_VSENN_WIDTH)
/*
* M98090_REG_BIAS_CONTROL 0x42
*/
#define M98090_VCM_MODE_MASK BIT(0)
#define M98090_VCM_MODE_SHIFT 0
#define M98090_VCM_MODE_WIDTH 1
#define M98090_VCM_MODE_NUM BIT(M98090_VCM_MODE_WIDTH)
/*
* M98090_REG_DAC_CONTROL 0x43
*/
#define M98090_PERFMODE_MASK BIT(1)
#define M98090_PERFMODE_SHIFT 1
#define M98090_PERFMODE_WIDTH 1
#define M98090_PERFMODE_NUM BIT(M98090_PERFMODE_WIDTH)
#define M98090_DACHP_MASK BIT(0)
#define M98090_DACHP_SHIFT 0
#define M98090_DACHP_WIDTH 1
#define M98090_DACHP_NUM BIT(M98090_DACHP_WIDTH)
/*
* M98090_REG_ADC_CONTROL 0x44
*/
#define M98090_OSR128_MASK BIT(2)
#define M98090_OSR128_SHIFT 2
#define M98090_OSR128_WIDTH 1
#define M98090_ADCDITHER_MASK BIT(1)
#define M98090_ADCDITHER_SHIFT 1
#define M98090_ADCDITHER_WIDTH 1
#define M98090_ADCDITHER_NUM BIT(M98090_ADCDITHER_WIDTH)
#define M98090_ADCHP_MASK BIT(0)
#define M98090_ADCHP_SHIFT 0
#define M98090_ADCHP_WIDTH 1
#define M98090_ADCHP_NUM BIT(M98090_ADCHP_WIDTH)
/*
* M98090_REG_DEVICE_SHUTDOWN 0x45
*/
#define M98090_SHDNN_MASK BIT(7)
#define M98090_SHDNN_SHIFT 7
#define M98090_SHDNN_WIDTH 1
/*
* M98090_REG_REVISION_ID 0xFF
*/
#define M98090_REVID_MASK (255 << 0)
#define M98090_REVID_SHIFT 0
#define M98090_REVID_WIDTH 8
#define M98090_REVID_NUM BIT(M98090_REVID_WIDTH)
/* function prototype */
/*
* initialise max98090 sound codec device for the given configuration
*
* @param blob FDT node for codec values
* @param sampling_rate Sampling rate (Hz)
* @param mclk_freq MCLK Frequency (Hz)
* @param bits_per_sample bits per Sample (must be 16 or 24)
*
* @returns -1 for error and 0 Success.
*/
int max98090_init(const void *blob, int sampling_rate, int mclk_freq,
int bits_per_sample);
int max98090_set_sysclk(struct maxim_priv *max98090, uint freq);
int max98090_hw_params(struct maxim_priv *max98090, uint rate,
uint bits_per_sample);
int max98090_device_init(struct maxim_priv *max98090);
int max98090_set_fmt(struct maxim_priv *max98090, int fmt);
#endif
@@ -0,0 +1,465 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* max98095.c -- MAX98095 ALSA SoC Audio driver
*
* Copyright 2011 Maxim Integrated Products
*
* Modified for U-Boot by R. Chandrasekar (rcsekar@samsung.com)
*/
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <div64.h>
#include <fdtdec.h>
#include <i2c.h>
#include <sound.h>
#include <asm/gpio.h>
#include "i2s.h"
#include "max98095.h"
/* Index 0 is reserved. */
int rate_table[] = {0, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000,
88200, 96000};
/*
* codec mclk clock divider coefficients based on sampling rate
*
* @param rate sampling rate
* @param value address of indexvalue to be stored
*
* @return 0 for success or negative error code.
*/
static int rate_value(int rate, u8 *value)
{
int i;
for (i = 1; i < ARRAY_SIZE(rate_table); i++) {
if (rate_table[i] >= rate) {
*value = i;
return 0;
}
}
*value = 1;
return -EINVAL;
}
/*
* Sets hw params for max98095
*
* @param priv max98095 information pointer
* @param rate Sampling rate
* @param bits_per_sample Bits per sample
*
* @return 0 for success or negative error code.
*/
static int max98095_hw_params(struct maxim_priv *priv,
enum en_max_audio_interface aif_id,
unsigned int rate, unsigned int bits_per_sample)
{
u8 regval;
int error;
unsigned short M98095_DAI_CLKMODE;
unsigned short M98095_DAI_FORMAT;
unsigned short M98095_DAI_FILTERS;
if (aif_id == AIF1) {
M98095_DAI_CLKMODE = M98095_027_DAI1_CLKMODE;
M98095_DAI_FORMAT = M98095_02A_DAI1_FORMAT;
M98095_DAI_FILTERS = M98095_02E_DAI1_FILTERS;
} else {
M98095_DAI_CLKMODE = M98095_031_DAI2_CLKMODE;
M98095_DAI_FORMAT = M98095_034_DAI2_FORMAT;
M98095_DAI_FILTERS = M98095_038_DAI2_FILTERS;
}
switch (bits_per_sample) {
case 16:
error = maxim_bic_or(priv, M98095_DAI_FORMAT, M98095_DAI_WS, 0);
break;
case 24:
error = maxim_bic_or(priv, M98095_DAI_FORMAT, M98095_DAI_WS,
M98095_DAI_WS);
break;
default:
debug("%s: Illegal bits per sample %d.\n",
__func__, bits_per_sample);
return -EINVAL;
}
if (rate_value(rate, &regval)) {
debug("%s: Failed to set sample rate to %d.\n",
__func__, rate);
return -EINVAL;
}
priv->rate = rate;
error |= maxim_bic_or(priv, M98095_DAI_CLKMODE, M98095_CLKMODE_MASK,
regval);
/* Update sample rate mode */
if (rate < 50000)
error |= maxim_bic_or(priv, M98095_DAI_FILTERS,
M98095_DAI_DHF, 0);
else
error |= maxim_bic_or(priv, M98095_DAI_FILTERS,
M98095_DAI_DHF, M98095_DAI_DHF);
if (error < 0) {
debug("%s: Error setting hardware params.\n", __func__);
return -EIO;
}
return 0;
}
/*
* Configures Audio interface system clock for the given frequency
*
* @param priv max98095 information
* @param freq Sampling frequency in Hz
*
* @return 0 for success or negative error code.
*/
static int max98095_set_sysclk(struct maxim_priv *priv, unsigned int freq)
{
int error = 0;
/* Requested clock frequency is already setup */
if (freq == priv->sysclk)
return 0;
/* Setup clocks for slave mode, and using the PLL
* PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
* 0x02 (when master clk is 20MHz to 40MHz)..
* 0x03 (when master clk is 40MHz to 60MHz)..
*/
if ((freq >= 10000000) && (freq < 20000000)) {
error = maxim_i2c_write(priv, M98095_026_SYS_CLK, 0x10);
} else if ((freq >= 20000000) && (freq < 40000000)) {
error = maxim_i2c_write(priv, M98095_026_SYS_CLK, 0x20);
} else if ((freq >= 40000000) && (freq < 60000000)) {
error = maxim_i2c_write(priv, M98095_026_SYS_CLK, 0x30);
} else {
debug("%s: Invalid master clock frequency\n", __func__);
return -EINVAL;
}
debug("%s: Clock at %uHz\n", __func__, freq);
if (error < 0)
return -EIO;
priv->sysclk = freq;
return 0;
}
/*
* Sets Max98095 I2S format
*
* @param priv max98095 information
* @param fmt i2S format - supports a subset of the options defined
* in i2s.h.
*
* @return 0 for success or negative error code.
*/
static int max98095_set_fmt(struct maxim_priv *priv, int fmt,
enum en_max_audio_interface aif_id)
{
u8 regval = 0;
int error = 0;
unsigned short M98095_DAI_CLKCFG_HI;
unsigned short M98095_DAI_CLKCFG_LO;
unsigned short M98095_DAI_FORMAT;
unsigned short M98095_DAI_CLOCK;
if (fmt == priv->fmt)
return 0;
priv->fmt = fmt;
if (aif_id == AIF1) {
M98095_DAI_CLKCFG_HI = M98095_028_DAI1_CLKCFG_HI;
M98095_DAI_CLKCFG_LO = M98095_029_DAI1_CLKCFG_LO;
M98095_DAI_FORMAT = M98095_02A_DAI1_FORMAT;
M98095_DAI_CLOCK = M98095_02B_DAI1_CLOCK;
} else {
M98095_DAI_CLKCFG_HI = M98095_032_DAI2_CLKCFG_HI;
M98095_DAI_CLKCFG_LO = M98095_033_DAI2_CLKCFG_LO;
M98095_DAI_FORMAT = M98095_034_DAI2_FORMAT;
M98095_DAI_CLOCK = M98095_035_DAI2_CLOCK;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* Slave mode PLL */
error |= maxim_i2c_write(priv, M98095_DAI_CLKCFG_HI, 0x80);
error |= maxim_i2c_write(priv, M98095_DAI_CLKCFG_LO, 0x00);
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* Set to master mode */
regval |= M98095_DAI_MAS;
break;
case SND_SOC_DAIFMT_CBS_CFM:
case SND_SOC_DAIFMT_CBM_CFS:
default:
debug("%s: Clock mode unsupported\n", __func__);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
regval |= M98095_DAI_DLY;
break;
case SND_SOC_DAIFMT_LEFT_J:
break;
default:
debug("%s: Unrecognized format.\n", __func__);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
regval |= M98095_DAI_WCI;
break;
case SND_SOC_DAIFMT_IB_NF:
regval |= M98095_DAI_BCI;
break;
case SND_SOC_DAIFMT_IB_IF:
regval |= M98095_DAI_BCI | M98095_DAI_WCI;
break;
default:
debug("%s: Unrecognized inversion settings.\n", __func__);
return -EINVAL;
}
error |= maxim_bic_or(priv, M98095_DAI_FORMAT,
M98095_DAI_MAS | M98095_DAI_DLY |
M98095_DAI_BCI | M98095_DAI_WCI, regval);
error |= maxim_i2c_write(priv, M98095_DAI_CLOCK, M98095_DAI_BSEL64);
if (error < 0) {
debug("%s: Error setting i2s format.\n", __func__);
return -EIO;
}
return 0;
}
/*
* resets the audio codec
*
* @param priv Private data for driver
* @return 0 for success or negative error code.
*/
static int max98095_reset(struct maxim_priv *priv)
{
int i, ret;
/*
* Gracefully reset the DSP core and the codec hardware in a proper
* sequence.
*/
ret = maxim_i2c_write(priv, M98095_00F_HOST_CFG, 0);
if (ret != 0) {
debug("%s: Failed to reset DSP: %d\n", __func__, ret);
return ret;
}
ret = maxim_i2c_write(priv, M98095_097_PWR_SYS, 0);
if (ret != 0) {
debug("%s: Failed to reset codec: %d\n", __func__, ret);
return ret;
}
/*
* Reset to hardware default for registers, as there is not a soft
* reset hardware control register.
*/
for (i = M98095_010_HOST_INT_CFG; i < M98095_REG_MAX_CACHED; i++) {
ret = maxim_i2c_write(priv, i, 0);
if (ret < 0) {
debug("%s: Failed to reset: %d\n", __func__, ret);
return ret;
}
}
return 0;
}
/*
* Intialise max98095 codec device
*
* @param priv max98095 information
* @return 0 for success or negative error code.
*/
static int max98095_device_init(struct maxim_priv *priv)
{
unsigned char id;
int ret;
/* reset the codec, the DSP core, and disable all interrupts */
ret = max98095_reset(priv);
if (ret != 0) {
debug("Reset\n");
return ret;
}
/* initialize private data */
priv->sysclk = -1U;
priv->rate = -1U;
priv->fmt = -1U;
ret = maxim_i2c_read(priv, M98095_0FF_REV_ID, &id);
if (ret < 0) {
debug("%s: Failure reading hardware revision: %d\n",
__func__, id);
return ret;
}
debug("%s: Hardware revision: %c\n", __func__, (id - 0x40) + 'A');
return 0;
}
static int max98095_setup_interface(struct maxim_priv *priv,
enum en_max_audio_interface aif_id)
{
int error;
error = maxim_i2c_write(priv, M98095_097_PWR_SYS, M98095_PWRSV);
/*
* initialize registers to hardware default configuring audio
* interface2 to DAC
*/
if (aif_id == AIF1)
error |= maxim_i2c_write(priv, M98095_048_MIX_DAC_LR,
M98095_DAI1L_TO_DACL |
M98095_DAI1R_TO_DACR);
else
error |= maxim_i2c_write(priv, M98095_048_MIX_DAC_LR,
M98095_DAI2M_TO_DACL |
M98095_DAI2M_TO_DACR);
error |= maxim_i2c_write(priv, M98095_092_PWR_EN_OUT,
M98095_SPK_SPREADSPECTRUM);
error |= maxim_i2c_write(priv, M98095_04E_CFG_HP, M98095_HPNORMAL);
if (aif_id == AIF1)
error |= maxim_i2c_write(priv, M98095_02C_DAI1_IOCFG,
M98095_S1NORMAL | M98095_SDATA);
else
error |= maxim_i2c_write(priv, M98095_036_DAI2_IOCFG,
M98095_S2NORMAL | M98095_SDATA);
/* take the codec out of the shut down */
error |= maxim_bic_or(priv, M98095_097_PWR_SYS, M98095_SHDNRUN,
M98095_SHDNRUN);
/*
* route DACL and DACR output to HO and Speakers
* Ordering: DACL, DACR, DACL, DACR
*/
error |= maxim_i2c_write(priv, M98095_050_MIX_SPK_LEFT, 0x01);
error |= maxim_i2c_write(priv, M98095_051_MIX_SPK_RIGHT, 0x01);
error |= maxim_i2c_write(priv, M98095_04C_MIX_HP_LEFT, 0x01);
error |= maxim_i2c_write(priv, M98095_04D_MIX_HP_RIGHT, 0x01);
/* power Enable */
error |= maxim_i2c_write(priv, M98095_091_PWR_EN_OUT, 0xF3);
/* set Volume */
error |= maxim_i2c_write(priv, M98095_064_LVL_HP_L, 15);
error |= maxim_i2c_write(priv, M98095_065_LVL_HP_R, 15);
error |= maxim_i2c_write(priv, M98095_067_LVL_SPK_L, 16);
error |= maxim_i2c_write(priv, M98095_068_LVL_SPK_R, 16);
/* Enable DAIs */
error |= maxim_i2c_write(priv, M98095_093_BIAS_CTRL, 0x30);
if (aif_id == AIF1)
error |= maxim_i2c_write(priv, M98095_096_PWR_DAC_CK, 0x01);
else
error |= maxim_i2c_write(priv, M98095_096_PWR_DAC_CK, 0x07);
if (error < 0)
return -EIO;
return 0;
}
static int max98095_do_init(struct maxim_priv *priv,
enum en_max_audio_interface aif_id,
int sampling_rate, int mclk_freq,
int bits_per_sample)
{
int ret = 0;
ret = max98095_setup_interface(priv, aif_id);
if (ret < 0) {
debug("%s: max98095 setup interface failed\n", __func__);
return ret;
}
ret = max98095_set_sysclk(priv, mclk_freq);
if (ret < 0) {
debug("%s: max98095 codec set sys clock failed\n", __func__);
return ret;
}
ret = max98095_hw_params(priv, aif_id, sampling_rate,
bits_per_sample);
if (ret == 0) {
ret = max98095_set_fmt(priv, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
aif_id);
}
return ret;
}
static int max98095_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample,
uint channels)
{
struct maxim_priv *priv = dev_get_priv(dev);
return max98095_do_init(priv, interface, rate, mclk_freq,
bits_per_sample);
}
static int max98095_probe(struct udevice *dev)
{
struct maxim_priv *priv = dev_get_priv(dev);
int ret;
priv->dev = dev;
ret = max98095_device_init(priv);
if (ret < 0) {
debug("%s: max98095 codec chip init failed\n", __func__);
return ret;
}
return 0;
}
static const struct audio_codec_ops max98095_ops = {
.set_params = max98095_set_params,
};
static const struct udevice_id max98095_ids[] = {
{ .compatible = "maxim,max98095" },
{ }
};
U_BOOT_DRIVER(max98095) = {
.name = "max98095",
.id = UCLASS_AUDIO_CODEC,
.of_match = max98095_ids,
.probe = max98095_probe,
.ops = &max98095_ops,
.priv_auto_alloc_size = sizeof(struct maxim_priv),
};
@@ -0,0 +1,316 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* max98095.h -- MAX98095 ALSA SoC Audio driver
*
* Copyright 2011 Maxim Integrated Products
*/
#ifndef _MAX98095_H
#define _MAX98095_H
#include "maxim_codec.h"
/* Available audio interface ports in wm8994 codec */
enum en_max_audio_interface {
AIF1,
AIF2,
};
/*
* MAX98095 Registers Definition
*/
#define M98095_000_HOST_DATA 0x00
#define M98095_001_HOST_INT_STS 0x01
#define M98095_002_HOST_RSP_STS 0x02
#define M98095_003_HOST_CMD_STS 0x03
#define M98095_004_CODEC_STS 0x04
#define M98095_005_DAI1_ALC_STS 0x05
#define M98095_006_DAI2_ALC_STS 0x06
#define M98095_007_JACK_AUTO_STS 0x07
#define M98095_008_JACK_MANUAL_STS 0x08
#define M98095_009_JACK_VBAT_STS 0x09
#define M98095_00A_ACC_ADC_STS 0x0A
#define M98095_00B_MIC_NG_AGC_STS 0x0B
#define M98095_00C_SPK_L_VOLT_STS 0x0C
#define M98095_00D_SPK_R_VOLT_STS 0x0D
#define M98095_00E_TEMP_SENSOR_STS 0x0E
#define M98095_00F_HOST_CFG 0x0F
#define M98095_010_HOST_INT_CFG 0x10
#define M98095_011_HOST_INT_EN 0x11
#define M98095_012_CODEC_INT_EN 0x12
#define M98095_013_JACK_INT_EN 0x13
#define M98095_014_JACK_INT_EN 0x14
#define M98095_015_DEC 0x15
#define M98095_016_RESERVED 0x16
#define M98095_017_RESERVED 0x17
#define M98095_018_KEYCODE3 0x18
#define M98095_019_KEYCODE2 0x19
#define M98095_01A_KEYCODE1 0x1A
#define M98095_01B_KEYCODE0 0x1B
#define M98095_01C_OEMCODE1 0x1C
#define M98095_01D_OEMCODE0 0x1D
#define M98095_01E_XCFG1 0x1E
#define M98095_01F_XCFG2 0x1F
#define M98095_020_XCFG3 0x20
#define M98095_021_XCFG4 0x21
#define M98095_022_XCFG5 0x22
#define M98095_023_XCFG6 0x23
#define M98095_024_XGPIO 0x24
#define M98095_025_XCLKCFG 0x25
#define M98095_026_SYS_CLK 0x26
#define M98095_027_DAI1_CLKMODE 0x27
#define M98095_028_DAI1_CLKCFG_HI 0x28
#define M98095_029_DAI1_CLKCFG_LO 0x29
#define M98095_02A_DAI1_FORMAT 0x2A
#define M98095_02B_DAI1_CLOCK 0x2B
#define M98095_02C_DAI1_IOCFG 0x2C
#define M98095_02D_DAI1_TDM 0x2D
#define M98095_02E_DAI1_FILTERS 0x2E
#define M98095_02F_DAI1_LVL1 0x2F
#define M98095_030_DAI1_LVL2 0x30
#define M98095_031_DAI2_CLKMODE 0x31
#define M98095_032_DAI2_CLKCFG_HI 0x32
#define M98095_033_DAI2_CLKCFG_LO 0x33
#define M98095_034_DAI2_FORMAT 0x34
#define M98095_035_DAI2_CLOCK 0x35
#define M98095_036_DAI2_IOCFG 0x36
#define M98095_037_DAI2_TDM 0x37
#define M98095_038_DAI2_FILTERS 0x38
#define M98095_039_DAI2_LVL1 0x39
#define M98095_03A_DAI2_LVL2 0x3A
#define M98095_03B_DAI3_CLKMODE 0x3B
#define M98095_03C_DAI3_CLKCFG_HI 0x3C
#define M98095_03D_DAI3_CLKCFG_LO 0x3D
#define M98095_03E_DAI3_FORMAT 0x3E
#define M98095_03F_DAI3_CLOCK 0x3F
#define M98095_040_DAI3_IOCFG 0x40
#define M98095_041_DAI3_TDM 0x41
#define M98095_042_DAI3_FILTERS 0x42
#define M98095_043_DAI3_LVL1 0x43
#define M98095_044_DAI3_LVL2 0x44
#define M98095_045_CFG_DSP 0x45
#define M98095_046_DAC_CTRL1 0x46
#define M98095_047_DAC_CTRL2 0x47
#define M98095_048_MIX_DAC_LR 0x48
#define M98095_049_MIX_DAC_M 0x49
#define M98095_04A_MIX_ADC_LEFT 0x4A
#define M98095_04B_MIX_ADC_RIGHT 0x4B
#define M98095_04C_MIX_HP_LEFT 0x4C
#define M98095_04D_MIX_HP_RIGHT 0x4D
#define M98095_04E_CFG_HP 0x4E
#define M98095_04F_MIX_RCV 0x4F
#define M98095_050_MIX_SPK_LEFT 0x50
#define M98095_051_MIX_SPK_RIGHT 0x51
#define M98095_052_MIX_SPK_CFG 0x52
#define M98095_053_MIX_LINEOUT1 0x53
#define M98095_054_MIX_LINEOUT2 0x54
#define M98095_055_MIX_LINEOUT_CFG 0x55
#define M98095_056_LVL_SIDETONE_DAI12 0x56
#define M98095_057_LVL_SIDETONE_DAI3 0x57
#define M98095_058_LVL_DAI1_PLAY 0x58
#define M98095_059_LVL_DAI1_EQ 0x59
#define M98095_05A_LVL_DAI2_PLAY 0x5A
#define M98095_05B_LVL_DAI2_EQ 0x5B
#define M98095_05C_LVL_DAI3_PLAY 0x5C
#define M98095_05D_LVL_ADC_L 0x5D
#define M98095_05E_LVL_ADC_R 0x5E
#define M98095_05F_LVL_MIC1 0x5F
#define M98095_060_LVL_MIC2 0x60
#define M98095_061_LVL_LINEIN 0x61
#define M98095_062_LVL_LINEOUT1 0x62
#define M98095_063_LVL_LINEOUT2 0x63
#define M98095_064_LVL_HP_L 0x64
#define M98095_065_LVL_HP_R 0x65
#define M98095_066_LVL_RCV 0x66
#define M98095_067_LVL_SPK_L 0x67
#define M98095_068_LVL_SPK_R 0x68
#define M98095_069_MICAGC_CFG 0x69
#define M98095_06A_MICAGC_THRESH 0x6A
#define M98095_06B_SPK_NOISEGATE 0x6B
#define M98095_06C_DAI1_ALC1_TIME 0x6C
#define M98095_06D_DAI1_ALC1_COMP 0x6D
#define M98095_06E_DAI1_ALC1_EXPN 0x6E
#define M98095_06F_DAI1_ALC1_GAIN 0x6F
#define M98095_070_DAI1_ALC2_TIME 0x70
#define M98095_071_DAI1_ALC2_COMP 0x71
#define M98095_072_DAI1_ALC2_EXPN 0x72
#define M98095_073_DAI1_ALC2_GAIN 0x73
#define M98095_074_DAI1_ALC3_TIME 0x74
#define M98095_075_DAI1_ALC3_COMP 0x75
#define M98095_076_DAI1_ALC3_EXPN 0x76
#define M98095_077_DAI1_ALC3_GAIN 0x77
#define M98095_078_DAI2_ALC1_TIME 0x78
#define M98095_079_DAI2_ALC1_COMP 0x79
#define M98095_07A_DAI2_ALC1_EXPN 0x7A
#define M98095_07B_DAI2_ALC1_GAIN 0x7B
#define M98095_07C_DAI2_ALC2_TIME 0x7C
#define M98095_07D_DAI2_ALC2_COMP 0x7D
#define M98095_07E_DAI2_ALC2_EXPN 0x7E
#define M98095_07F_DAI2_ALC2_GAIN 0x7F
#define M98095_080_DAI2_ALC3_TIME 0x80
#define M98095_081_DAI2_ALC3_COMP 0x81
#define M98095_082_DAI2_ALC3_EXPN 0x82
#define M98095_083_DAI2_ALC3_GAIN 0x83
#define M98095_084_HP_NOISE_GATE 0x84
#define M98095_085_AUX_ADC 0x85
#define M98095_086_CFG_LINE 0x86
#define M98095_087_CFG_MIC 0x87
#define M98095_088_CFG_LEVEL 0x88
#define M98095_089_JACK_DET_AUTO 0x89
#define M98095_08A_JACK_DET_MANUAL 0x8A
#define M98095_08B_JACK_KEYSCAN_DBC 0x8B
#define M98095_08C_JACK_KEYSCAN_DLY 0x8C
#define M98095_08D_JACK_KEY_THRESH 0x8D
#define M98095_08E_JACK_DC_SLEW 0x8E
#define M98095_08F_JACK_TEST_CFG 0x8F
#define M98095_090_PWR_EN_IN 0x90
#define M98095_091_PWR_EN_OUT 0x91
#define M98095_092_PWR_EN_OUT 0x92
#define M98095_093_BIAS_CTRL 0x93
#define M98095_094_PWR_DAC_21 0x94
#define M98095_095_PWR_DAC_03 0x95
#define M98095_096_PWR_DAC_CK 0x96
#define M98095_097_PWR_SYS 0x97
#define M98095_0FF_REV_ID 0xFF
#define M98095_REG_CNT (0xFF+1)
#define M98095_REG_MAX_CACHED 0X97
/* MAX98095 Registers Bit Fields */
/* M98095_00F_HOST_CFG */
#define M98095_SEG (1<<0)
#define M98095_XTEN (1<<1)
#define M98095_MDLLEN (1<<2)
/* M98095_027_DAI1_CLKMODE, M98095_031_DAI2_CLKMODE, M98095_03B_DAI3_CLKMODE */
#define M98095_CLKMODE_MASK 0xFF
/* M98095_02A_DAI1_FORMAT, M98095_034_DAI2_FORMAT, M98095_03E_DAI3_FORMAT */
#define M98095_DAI_MAS (1<<7)
#define M98095_DAI_WCI (1<<6)
#define M98095_DAI_BCI (1<<5)
#define M98095_DAI_DLY (1<<4)
#define M98095_DAI_TDM (1<<2)
#define M98095_DAI_FSW (1<<1)
#define M98095_DAI_WS (1<<0)
/* M98095_02B_DAI1_CLOCK, M98095_035_DAI2_CLOCK, M98095_03F_DAI3_CLOCK */
#define M98095_DAI_BSEL64 (1<<0)
#define M98095_DAI_DOSR_DIV2 (0<<5)
#define M98095_DAI_DOSR_DIV4 (1<<5)
/* M98095_02C_DAI1_IOCFG, M98095_036_DAI2_IOCFG, M98095_040_DAI3_IOCFG */
#define M98095_S1NORMAL (1<<6)
#define M98095_S2NORMAL (2<<6)
#define M98095_S3NORMAL (3<<6)
#define M98095_SDATA (3<<0)
/* M98095_02E_DAI1_FILTERS, M98095_038_DAI2_FILTERS, M98095_042_DAI3_FILTERS */
#define M98095_DAI_DHF (1<<3)
/* M98095_045_DSP_CFG */
#define M98095_DSPNORMAL (5<<4)
/* M98095_048_MIX_DAC_LR */
#define M98095_DAI1L_TO_DACR (1<<7)
#define M98095_DAI1R_TO_DACR (1<<6)
#define M98095_DAI2M_TO_DACR (1<<5)
#define M98095_DAI1L_TO_DACL (1<<3)
#define M98095_DAI1R_TO_DACL (1<<2)
#define M98095_DAI2M_TO_DACL (1<<1)
#define M98095_DAI3M_TO_DACL (1<<0)
/* M98095_049_MIX_DAC_M */
#define M98095_DAI1L_TO_DACM (1<<3)
#define M98095_DAI1R_TO_DACM (1<<2)
#define M98095_DAI2M_TO_DACM (1<<1)
#define M98095_DAI3M_TO_DACM (1<<0)
/* M98095_04E_MIX_HP_CFG */
#define M98095_HPNORMAL (3<<4)
/* M98095_05F_LVL_MIC1, M98095_060_LVL_MIC2 */
#define M98095_MICPRE_MASK (3<<5)
#define M98095_MICPRE_SHIFT 5
/* M98095_064_LVL_HP_L, M98095_065_LVL_HP_R */
#define M98095_HP_MUTE (1<<7)
/* M98095_066_LVL_RCV */
#define M98095_REC_MUTE (1<<7)
/* M98095_067_LVL_SPK_L, M98095_068_LVL_SPK_R */
#define M98095_SP_MUTE (1<<7)
/* M98095_087_CFG_MIC */
#define M98095_MICSEL_MASK (3<<0)
#define M98095_DIGMIC_L (1<<2)
#define M98095_DIGMIC_R (1<<3)
#define M98095_DIGMIC2L (1<<4)
#define M98095_DIGMIC2R (1<<5)
/* M98095_088_CFG_LEVEL */
#define M98095_VSEN (1<<6)
#define M98095_ZDEN (1<<5)
#define M98095_BQ2EN (1<<3)
#define M98095_BQ1EN (1<<2)
#define M98095_EQ2EN (1<<1)
#define M98095_EQ1EN (1<<0)
/* M98095_090_PWR_EN_IN */
#define M98095_INEN (1<<7)
#define M98095_MB2EN (1<<3)
#define M98095_MB1EN (1<<2)
#define M98095_MBEN (3<<2)
#define M98095_ADREN (1<<1)
#define M98095_ADLEN (1<<0)
/* M98095_091_PWR_EN_OUT */
#define M98095_HPLEN (1<<7)
#define M98095_HPREN (1<<6)
#define M98095_SPLEN (1<<5)
#define M98095_SPREN (1<<4)
#define M98095_RECEN (1<<3)
#define M98095_DALEN (1<<1)
#define M98095_DAREN (1<<0)
/* M98095_092_PWR_EN_OUT */
#define M98095_SPK_FIXEDSPECTRUM (0<<4)
#define M98095_SPK_SPREADSPECTRUM (1<<4)
/* M98095_097_PWR_SYS */
#define M98095_SHDNRUN (1<<7)
#define M98095_PERFMODE (1<<3)
#define M98095_HPPLYBACK (1<<2)
#define M98095_PWRSV8K (1<<1)
#define M98095_PWRSV (1<<0)
#define M98095_COEFS_PER_BAND 5
/* Equalizer filter coefficients */
#define M98095_110_DAI1_EQ_BASE 0x10
#define M98095_142_DAI2_EQ_BASE 0x42
/* Biquad filter coefficients */
#define M98095_174_DAI1_BQ_BASE 0x74
#define M98095_17E_DAI2_BQ_BASE 0x7E
/* function prototype */
/*
* intialise max98095 sound codec device for the given configuration
*
* @param blob FDT node for codec values
* @param sampling_rate Sampling rate (Hz)
* @param mclk_freq MCLK Frequency (Hz)
* @param bits_per_sample bits per Sample (must be 16 or 24)
*
* @returns -1 for error and 0 Success.
*/
int max98095_init(const void *blob, enum en_max_audio_interface aif_id,
int sampling_rate, int mclk_freq, int bits_per_sample);
#endif
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/*
* maxim_codec.c -- MAXIM CODEC Common driver
*
* Copyright 2011 Maxim Integrated Products
*/
#include <common.h>
#include <div64.h>
#include <i2c.h>
#include <i2s.h>
#include <sound.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include "maxim_codec.h"
/*
* Writes value to a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be write
* @param data data to be writen to the above registor
*
* @return int value 1 for change, 0 for no change or negative error code.
*/
int maxim_i2c_write(struct maxim_priv *priv, unsigned int reg,
unsigned char data)
{
debug("%s: Write Addr : 0x%02X, Data : 0x%02X\n",
__func__, reg, data);
return dm_i2c_write(priv->dev, reg, &data, 1);
}
/*
* Read a value from a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be read
* @param data address of read data to be stored
*
* @return int value 0 for success, -1 in case of error.
*/
unsigned int maxim_i2c_read(struct maxim_priv *priv, unsigned int reg,
unsigned char *data)
{
int ret;
return dm_i2c_read(priv->dev, reg, data, 1);
if (ret != 0) {
debug("%s: Error while reading register %#04x\n",
__func__, reg);
return -1;
}
return 0;
}
/*
* update device register bits through i2c
*
* @param priv Private data for driver
* @param reg codec register
* @param mask register mask
* @param value new value
*
* @return int value 0 for success, non-zero error code.
*/
int maxim_bic_or(struct maxim_priv *priv, unsigned int reg, unsigned char mask,
unsigned char value)
{
int change, ret = 0;
unsigned char old, new;
if (maxim_i2c_read(priv, reg, &old) != 0)
return -1;
new = (old & ~mask) | (value & mask);
change = (old != new) ? 1 : 0;
if (change)
ret = maxim_i2c_write(priv, reg, new);
if (ret < 0)
return ret;
return change;
}
@@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* maxim_codec.h -- MAXIM codec common interface file
*
* Copyright (C) 2013 Samsung Electronics
* D Krishna Mohan <krishna.md@samsung.com>
*/
#ifndef __MAXIM_COMMON_H__
#define __MAXIM_COMMON_H__
enum maxim_codec_type {
MAX98095,
MAX98090,
};
struct maxim_priv {
enum maxim_codec_type devtype;
unsigned int sysclk;
unsigned int rate;
unsigned int fmt;
struct udevice *dev;
};
#define MAXIM_AUDIO_I2C_BUS 7
#define MAXIM_AUDIO_I2C_REG_98095 0x22
#define MAXIM_AUDIO_I2C_REG MAXIM_AUDIO_I2C_REG_98095
/*
* Writes value to a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be write
* @param data data to be writen to the above registor
*
* @return int value 1 for change, 0 for no change or negative error code.
*/
int maxim_i2c_write(struct maxim_priv *priv, unsigned int reg,
unsigned char data);
/*
* Read a value from a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be read
* @param data address of read data to be stored
*
* @return int value 0 for success, -1 in case of error.
*/
unsigned int maxim_i2c_read(struct maxim_priv *priv, unsigned int reg,
unsigned char *data);
/*
* update device register bits through i2c
*
* @param priv Private data for driver
* @param reg codec register
* @param mask register mask
* @param value new value
*
* @return int value 0 for success, non-zero error code.
*/
int maxim_bic_or(struct maxim_priv *priv, unsigned int reg, unsigned char mask,
unsigned char value);
#endif /* __MAXIM_COMMON_H__ */
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Copyright 2014 Rockchip Electronics Co., Ltd.
* Taken from dc i2s/rockchip.c
*/
#define LOG_CATEGORY UCLASS_I2S
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
#include <asm/io.h>
struct rk_i2s_regs {
u32 txcr; /* I2S_TXCR, 0x00 */
u32 rxcr; /* I2S_RXCR, 0x04 */
u32 ckr; /* I2S_CKR, 0x08 */
u32 fifolr; /* I2S_FIFOLR, 0x0C */
u32 dmacr; /* I2S_DMACR, 0x10 */
u32 intcr; /* I2S_INTCR, 0x14 */
u32 intsr; /* I2S_INTSR, 0x18 */
u32 xfer; /* I2S_XFER, 0x1C */
u32 clr; /* I2S_CLR, 0x20 */
u32 txdr; /* I2S_TXDR, 0x24 */
u32 rxdr; /* I2S_RXDR, 0x28 */
};
enum {
/* I2S_XFER */
I2S_RX_TRAN_BIT = BIT(1),
I2S_TX_TRAN_BIT = BIT(0),
I2S_TRAN_MASK = 3 << 0,
/* I2S_TXCKR */
I2S_MCLK_DIV_SHIFT = 16,
I2S_MCLK_DIV_MASK = (0xff << I2S_MCLK_DIV_SHIFT),
I2S_RX_SCLK_DIV_SHIFT = 8,
I2S_RX_SCLK_DIV_MASK = 0xff << I2S_RX_SCLK_DIV_SHIFT,
I2S_TX_SCLK_DIV_SHIFT = 0,
I2S_TX_SCLK_DIV_MASK = 0xff << I2S_TX_SCLK_DIV_SHIFT,
I2S_DATA_WIDTH_SHIFT = 0,
I2S_DATA_WIDTH_MASK = 0x1f << I2S_DATA_WIDTH_SHIFT,
};
static int rockchip_i2s_init(struct i2s_uc_priv *priv)
{
struct rk_i2s_regs *regs = (struct rk_i2s_regs *)priv->base_address;
u32 bps = priv->bitspersample;
u32 lrf = priv->rfs;
u32 chn = priv->channels;
u32 mode = 0;
clrbits_le32(&regs->xfer, I2S_TX_TRAN_BIT);
mode = readl(&regs->txcr) & ~0x1f;
switch (priv->bitspersample) {
case 16:
case 24:
mode |= (priv->bitspersample - 1) << I2S_DATA_WIDTH_SHIFT;
break;
default:
log_err("Invalid sample size input %d\n", priv->bitspersample);
return -EINVAL;
}
writel(mode, &regs->txcr);
mode = readl(&regs->ckr) & ~I2S_MCLK_DIV_MASK;
mode |= (lrf / (bps * chn) - 1) << I2S_MCLK_DIV_SHIFT;
mode &= ~I2S_TX_SCLK_DIV_MASK;
mode |= (priv->bitspersample * priv->channels - 1) <<
I2S_TX_SCLK_DIV_SHIFT;
writel(mode, &regs->ckr);
return 0;
}
static int i2s_send_data(struct rk_i2s_regs *regs, u32 *data, uint length)
{
for (int i = 0; i < min(32u, length); i++)
writel(*data++, &regs->txdr);
length -= min(32u, length);
/* enable both tx and rx */
setbits_le32(&regs->xfer, I2S_TRAN_MASK);
while (length) {
if ((readl(&regs->fifolr) & 0x3f) < 0x20) {
writel(*data++, &regs->txdr);
length--;
}
}
while (readl(&regs->fifolr) & 0x3f)
/* wait until FIFO empty */;
clrbits_le32(&regs->xfer, I2S_TRAN_MASK);
writel(0, &regs->clr);
return 0;
}
static int rockchip_i2s_tx_data(struct udevice *dev, void *data, uint data_size)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
struct rk_i2s_regs *regs = (struct rk_i2s_regs *)priv->base_address;
return i2s_send_data(regs, data, data_size / sizeof(u32));
}
static int rockchip_i2s_probe(struct udevice *dev)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
ulong base;
base = dev_read_addr(dev);
if (base == FDT_ADDR_T_NONE) {
log_debug("Missing i2s base\n");
return -EINVAL;
}
priv->base_address = base;
priv->id = 1;
priv->audio_pll_clk = 4800000;
priv->samplingrate = 48000;
priv->bitspersample = 16;
priv->channels = 2;
priv->rfs = 256;
priv->bfs = 32;
return rockchip_i2s_init(priv);
}
static const struct i2s_ops rockchip_i2s_ops = {
.tx_data = rockchip_i2s_tx_data,
};
static const struct udevice_id rockchip_i2s_ids[] = {
{ .compatible = "rockchip,rk3288-i2s" },
{ }
};
U_BOOT_DRIVER(rockchip_i2s) = {
.name = "rockchip_i2s",
.id = UCLASS_I2S,
.of_match = rockchip_i2s_ids,
.probe = rockchip_i2s_probe,
.ops = &rockchip_i2s_ops,
};
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google, LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <audio_codec.h>
#include <clk.h>
#include <dm.h>
#include <i2s.h>
#include <misc.h>
#include <sound.h>
#include <asm/arch-rockchip/periph.h>
#include <dm/pinctrl.h>
static int rockchip_sound_setup(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct i2s_uc_priv *i2c_priv = dev_get_uclass_priv(uc_priv->i2s);
int ret;
if (uc_priv->setup_done)
return -EALREADY;
ret = audio_codec_set_params(uc_priv->codec, i2c_priv->id,
i2c_priv->samplingrate,
i2c_priv->samplingrate * i2c_priv->rfs,
i2c_priv->bitspersample,
i2c_priv->channels);
if (ret)
return ret;
uc_priv->setup_done = true;
return 0;
}
static int rockchip_sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
return i2s_tx_data(uc_priv->i2s, data, data_size);
}
static int rockchip_sound_probe(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct ofnode_phandle_args args;
struct udevice *pinctrl;
struct clk clk;
ofnode node;
int ret;
node = ofnode_find_subnode(dev_ofnode(dev), "cpu");
if (!ofnode_valid(node)) {
log_debug("Failed to find /cpu subnode\n");
return -EINVAL;
}
ret = ofnode_parse_phandle_with_args(node, "sound-dai",
"#sound-dai-cells", 0, 0, &args);
if (ret) {
log_debug("Cannot find i2s phandle: %d\n", ret);
return ret;
}
ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s);
if (ret) {
log_debug("Cannot find i2s: %d\n", ret);
return ret;
}
node = ofnode_find_subnode(dev_ofnode(dev), "codec");
if (!ofnode_valid(node)) {
log_debug("Failed to find /codec subnode\n");
return -EINVAL;
}
ret = ofnode_parse_phandle_with_args(node, "sound-dai",
"#sound-dai-cells", 0, 0, &args);
if (ret) {
log_debug("Cannot find codec phandle: %d\n", ret);
return ret;
}
ret = uclass_get_device_by_ofnode(UCLASS_AUDIO_CODEC, args.node,
&uc_priv->codec);
if (ret) {
log_debug("Cannot find audio codec: %d\n", ret);
return ret;
}
ret = clk_get_by_index(uc_priv->i2s, 1, &clk);
if (ret) {
log_debug("Cannot find clock: %d\n", ret);
return ret;
}
ret = clk_set_rate(&clk, 12288000);
if (ret < 0) {
log_debug("Cannot find clock: %d\n", ret);
return ret;
}
ret = uclass_get_device(UCLASS_PINCTRL, 0, &pinctrl);
if (ret) {
debug("%s: Cannot find pinctrl device\n", __func__);
return ret;
}
ret = pinctrl_request(pinctrl, PERIPH_ID_I2S, 0);
if (ret) {
debug("%s: Cannot select I2C pinctrl\n", __func__);
return ret;
}
log_debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name,
uc_priv->codec->name, uc_priv->i2s->name);
return 0;
}
static const struct sound_ops rockchip_sound_ops = {
.setup = rockchip_sound_setup,
.play = rockchip_sound_play,
};
static const struct udevice_id rockchip_sound_ids[] = {
{ .compatible = "rockchip,audio-max98090-jerry" },
{ }
};
U_BOOT_DRIVER(rockchip_sound) = {
.name = "rockchip_sound",
.id = UCLASS_SOUND,
.of_match = rockchip_sound_ids,
.probe = rockchip_sound_probe,
.ops = &rockchip_sound_ops,
};
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2019 Google LLC
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <i2c.h>
#include "rt5677.h"
struct rt5677_priv {
struct udevice *dev;
};
/* RT5677 has 256 8-bit register addresses, and 16-bit register data */
struct rt5677_init_reg {
u8 reg;
u16 val;
};
static struct rt5677_init_reg init_list[] = {
{RT5677_LOUT1, 0x0800},
{RT5677_SIDETONE_CTRL, 0x0000},
{RT5677_STO1_ADC_DIG_VOL, 0x3F3F},
{RT5677_DAC1_DIG_VOL, 0x9090},
{RT5677_STO2_ADC_MIXER, 0xA441},
{RT5677_STO1_ADC_MIXER, 0x5480},
{RT5677_STO1_DAC_MIXER, 0x8A8A},
{RT5677_PWR_DIG1, 0x9800}, /* Power up I2S1 */
{RT5677_PWR_ANLG1, 0xE9D5},
{RT5677_PWR_ANLG2, 0x2CC0},
{RT5677_PWR_DSP2, 0x0C00},
{RT5677_I2S2_SDP, 0x0000},
{RT5677_CLK_TREE_CTRL1, 0x1111},
{RT5677_PLL1_CTRL1, 0x0000},
{RT5677_PLL1_CTRL2, 0x0000},
{RT5677_DIG_MISC, 0x0029},
{RT5677_GEN_CTRL1, 0x00FF},
{RT5677_GPIO_CTRL2, 0x0020},
{RT5677_PWR_DIG2, 0x9024}, /* Power on ADC Stereo Filters */
{RT5677_PDM_OUT_CTRL, 0x0088}, /* Unmute PDM, set stereo1 DAC */
{RT5677_PDM_DATA_CTRL1, 0x0001}, /* Sysclk to PDM filter divider 2 */
};
/**
* rt5677_i2c_read() - Read a 16-bit register
*
* @priv: Private driver data
* @reg: Register number to read
* @returns data read or -ve on error
*/
static int rt5677_i2c_read(struct rt5677_priv *priv, uint reg)
{
u8 buf[2];
int ret;
ret = dm_i2c_read(priv->dev, reg, buf, sizeof(u16));
if (ret)
return ret;
return buf[0] << 8 | buf[1];
}
/**
* rt5677_i2c_write() - Write a 16-bit register
*
* @priv: Private driver data
* @reg: Register number to read
* @data: Data to write
* @returns 0 if OK, -ve on error
*/
static int rt5677_i2c_write(struct rt5677_priv *priv, uint reg, uint data)
{
u8 buf[2];
buf[0] = (data >> 8) & 0xff;
buf[1] = data & 0xff;
return dm_i2c_write(priv->dev, reg, buf, sizeof(u16));
}
/**
* rt5677_bic_or() - Set and clear bits of a codec register
*
* @priv: Private driver data
* @reg: Register number to update
* @bic: Mask of bits to clear
* @set: Mask of bits to set
* @returns 0 if OK, -ve on error
*
*/
static int rt5677_bic_or(struct rt5677_priv *priv, uint reg, uint bic,
uint set)
{
uint old, new_value;
int ret;
old = rt5677_i2c_read(priv, reg);
if (old < 0)
return old;
new_value = (old & ~bic) | (set & bic);
if (old != new_value) {
ret = rt5677_i2c_write(priv, reg, new_value);
if (ret)
return ret;
}
return 0;
}
/**
* rt5677_reg_init() - Initialise codec regs w/static/base values
*
* @priv: Private driver data
* @returns 0 if OK, -ve on error
*/
static int rt5677_reg_init(struct rt5677_priv *priv)
{
int ret;
int i;
for (i = 0; i < ARRAY_SIZE(init_list); i++) {
ret = rt5677_i2c_write(priv, init_list[i].reg, init_list[i].val);
if (ret)
return ret;
}
return 0;
}
#ifdef DEBUG
static void debug_dump_5677_regs(struct rt5677_priv *priv, int swap)
{
uint i, reg_word;
/* Show all 16-bit codec regs */
for (i = 0; i < RT5677_REG_CNT; i++) {
if (i % 8 == 0)
log_debug("\nMX%02x: ", i);
rt5677_i2c_read(priv, (u8)i, &reg_word);
if (swap)
log_debug("%04x ", swap_bytes16(reg_word));
else
log_debug("%04x ", reg_word);
}
log_debug("\n");
/* Show all 16-bit 'private' codec regs */
for (i = 0; i < RT5677_PR_REG_CNT; i++) {
if (i % 8 == 0)
log_debug("\nPR%02x: ", i);
rt5677_i2c_write(priv, RT5677_PRIV_INDEX, i);
rt5677_i2c_read(priv, RT5677_PRIV_DATA, &reg_word);
if (swap)
log_debug("%04x ", swap_bytes16(reg_word));
else
log_debug("%04x ", reg_word);
}
log_debug("\n");
}
#endif /* DEBUG */
static int rt5677_hw_params(struct rt5677_priv *priv, uint bits_per_sample)
{
int ret;
switch (bits_per_sample) {
case 16:
ret = rt5677_bic_or(priv, RT5677_I2S1_SDP, RT5677_I2S_DL_MASK,
0);
if (ret) {
log_debug("Error updating I2S1 Interface Ctrl reg\n");
return 1;
}
break;
default:
log_err("Illegal bits per sample %d\n", bits_per_sample);
return -EINVAL;
}
return 0;
}
/**
* rt5677_set_fmt() - set rt5677 I2S format
*
* @priv: Private driver data
* @returns 0 if OK, -ve on error
*/
static int rt5677_set_fmt(struct rt5677_priv *priv)
{
int ret = 0;
/*
* Set format here: Assumes I2S, NB_NF, CBS_CFS
*
* CBS_CFS (Codec Bit Slave/Codec Frame Slave)
*/
ret = rt5677_bic_or(priv, RT5677_I2S1_SDP, RT5677_I2S_MS_MASK,
RT5677_I2S_MS_S);
/* NB_NF (Normal Bit/Normal Frame) */
ret |= rt5677_bic_or(priv, RT5677_I2S1_SDP, RT5677_I2S_BP_MASK,
RT5677_I2S_BP_NOR);
/* I2S mode */
ret |= rt5677_bic_or(priv, RT5677_I2S1_SDP, RT5677_I2S_DF_MASK,
RT5677_I2S_DF_I2S);
/* A44: I2S2 (going to speaker amp) is master */
ret |= rt5677_bic_or(priv, RT5677_I2S2_SDP, RT5677_I2S_MS_MASK,
RT5677_I2S_MS_M);
if (ret) {
log_err("Error updating I2S1 Interface Ctrl reg\n");
return ret;
}
return 0;
}
/**
* rt5677_reset() - reset the audio codec
*
* @priv: Private driver data
* @returns 0 if OK, -ve on error
*/
static int rt5677_reset(struct rt5677_priv *priv)
{
int ret;
/* Reset the codec registers to their defaults */
ret = rt5677_i2c_write(priv, RT5677_RESET, RT5677_SW_RESET);
if (ret) {
log_err("Error resetting codec\n");
return ret;
}
return 0;
}
/**
* Initialise rt5677 codec device
*
* @priv: Private driver data
* @returns 0 if OK, -ve on error
*/
int rt5677_device_init(struct rt5677_priv *priv)
{
int ret;
/* Read status reg */
ret = rt5677_i2c_read(priv, RT5677_RESET);
if (ret < 0)
return ret;
log_debug("reg 00h, Software Reset & Status = 0x%04x\n", ret);
/* Reset the codec/regs */
ret = rt5677_reset(priv);
if (ret)
return ret;
ret = rt5677_i2c_read(priv, RT5677_VENDOR_ID1);
if (ret < 0) {
log_err("Error reading vendor ID\n");
return 1;
}
log_debug("Hardware ID: %0xX\n", ret);
ret = rt5677_i2c_read(priv, RT5677_VENDOR_ID2);
if (ret < 0) {
log_err("Error reading vendor rev\n");
return 1;
}
log_debug("Hardware revision: %04x\n", ret);
return 0;
}
static int rt5677_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample,
uint channels)
{
struct rt5677_priv *priv = dev_get_priv(dev);
int ret;
/* Initialise codec regs w/static/base values, same as Linux driver */
ret = rt5677_reg_init(priv);
if (ret)
return ret;
ret = rt5677_hw_params(priv, bits_per_sample);
if (ret)
return ret;
ret = rt5677_set_fmt(priv);
if (ret)
return ret;
return 0;
}
static int rt5677_probe(struct udevice *dev)
{
struct rt5677_priv *priv = dev_get_priv(dev);
priv->dev = dev;
return rt5677_device_init(priv);
}
static const struct audio_codec_ops rt5677_ops = {
.set_params = rt5677_set_params,
};
static const struct udevice_id rt5677_ids[] = {
{ .compatible = "realtek,rt5677" },
{ }
};
U_BOOT_DRIVER(rt5677_drv) = {
.name = "rt5677",
.id = UCLASS_AUDIO_CODEC,
.of_match = rt5677_ids,
.ops = &rt5677_ops,
.probe = rt5677_probe,
.priv_auto_alloc_size = sizeof(struct rt5677_priv),
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 Samsung Electronics
* R. Chandrasekar <rcsekar@samsung.com>
*/
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
#include <asm/arch/clk.h>
#include <asm/arch/pinmux.h>
#include <asm/arch/i2s-regs.h>
#include <asm/io.h>
#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
#define TIMEOUT_I2S_TX 100 /* i2s transfer timeout */
/*
* Sets the frame size for I2S LR clock
*
* @param i2s_reg i2s register address
* @param rfs Frame Size
*/
static void i2s_set_lr_framesize(struct i2s_reg *i2s_reg, unsigned int rfs)
{
unsigned int mod = readl(&i2s_reg->mod);
mod &= ~MOD_RCLK_MASK;
switch (rfs) {
case 768:
mod |= MOD_RCLK_768FS;
break;
case 512:
mod |= MOD_RCLK_512FS;
break;
case 384:
mod |= MOD_RCLK_384FS;
break;
default:
mod |= MOD_RCLK_256FS;
break;
}
writel(mod, &i2s_reg->mod);
}
/*
* Sets the i2s transfer control
*
* @param i2s_reg i2s register address
* @param on 1 enable tx , 0 disable tx transfer
*/
static void i2s_txctrl(struct i2s_reg *i2s_reg, int on)
{
unsigned int con = readl(&i2s_reg->con);
unsigned int mod = readl(&i2s_reg->mod) & ~MOD_MASK;
if (on) {
con |= CON_ACTIVE;
con &= ~CON_TXCH_PAUSE;
} else {
con |= CON_TXCH_PAUSE;
con &= ~CON_ACTIVE;
}
writel(mod, &i2s_reg->mod);
writel(con, &i2s_reg->con);
}
/*
* set the bit clock frame size (in multiples of LRCLK)
*
* @param i2s_reg i2s register address
* @param bfs bit Frame Size
*/
static void i2s_set_bitclk_framesize(struct i2s_reg *i2s_reg, unsigned bfs)
{
unsigned int mod = readl(&i2s_reg->mod);
mod &= ~MOD_BCLK_MASK;
switch (bfs) {
case 48:
mod |= MOD_BCLK_48FS;
break;
case 32:
mod |= MOD_BCLK_32FS;
break;
case 24:
mod |= MOD_BCLK_24FS;
break;
case 16:
mod |= MOD_BCLK_16FS;
break;
default:
return;
}
writel(mod, &i2s_reg->mod);
}
/*
* flushes the i2stx fifo
*
* @param i2s_reg i2s register address
* @param flush Tx fifo flush command (0x00 - do not flush
* 0x80 - flush tx fifo)
*/
static void i2s_fifo(struct i2s_reg *i2s_reg, unsigned int flush)
{
/* Flush the FIFO */
setbits_le32(&i2s_reg->fic, flush);
clrbits_le32(&i2s_reg->fic, flush);
}
/*
* Set System Clock direction
*
* @param i2s_reg i2s register address
* @param dir Clock direction
*
* @return int value 0 for success, -1 in case of error
*/
static int i2s_set_sysclk_dir(struct i2s_reg *i2s_reg, int dir)
{
unsigned int mod = readl(&i2s_reg->mod);
if (dir == SND_SOC_CLOCK_IN)
mod |= MOD_CDCLKCON;
else
mod &= ~MOD_CDCLKCON;
writel(mod, &i2s_reg->mod);
return 0;
}
/*
* Sets I2S Clcok format
*
* @param fmt i2s clock properties
* @param i2s_reg i2s register address
*
* @return int value 0 for success, -1 in case of error
*/
static int i2s_set_fmt(struct i2s_reg *i2s_reg, unsigned int fmt)
{
unsigned int mod = readl(&i2s_reg->mod);
unsigned int tmp = 0;
unsigned int ret = 0;
/* Format is priority */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_RIGHT_J:
tmp |= MOD_LR_RLOW;
tmp |= MOD_SDF_MSB;
break;
case SND_SOC_DAIFMT_LEFT_J:
tmp |= MOD_LR_RLOW;
tmp |= MOD_SDF_LSB;
break;
case SND_SOC_DAIFMT_I2S:
tmp |= MOD_SDF_IIS;
break;
default:
debug("%s: Invalid format priority [0x%x]\n", __func__,
(fmt & SND_SOC_DAIFMT_FORMAT_MASK));
return -ERANGE;
}
/*
* INV flag is relative to the FORMAT flag - if set it simply
* flips the polarity specified by the Standard
*/
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
if (tmp & MOD_LR_RLOW)
tmp &= ~MOD_LR_RLOW;
else
tmp |= MOD_LR_RLOW;
break;
default:
debug("%s: Invalid clock ploarity input [0x%x]\n", __func__,
(fmt & SND_SOC_DAIFMT_INV_MASK));
return -ERANGE;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
tmp |= MOD_SLAVE;
break;
case SND_SOC_DAIFMT_CBM_CFM:
/* Set default source clock in Master mode */
ret = i2s_set_sysclk_dir(i2s_reg, SND_SOC_CLOCK_OUT);
if (ret != 0) {
debug("%s:set i2s clock direction failed\n", __func__);
return ret;
}
break;
default:
debug("%s: Invalid master selection [0x%x]\n", __func__,
(fmt & SND_SOC_DAIFMT_MASTER_MASK));
return -ERANGE;
}
mod &= ~(MOD_SDF_MASK | MOD_LR_RLOW | MOD_SLAVE);
mod |= tmp;
writel(mod, &i2s_reg->mod);
return 0;
}
/*
* Sets the sample width in bits
*
* @param blc samplewidth (size of sample in bits)
* @param i2s_reg i2s register address
*
* @return int value 0 for success, -1 in case of error
*/
static int i2s_set_samplesize(struct i2s_reg *i2s_reg, unsigned int blc)
{
unsigned int mod = readl(&i2s_reg->mod);
mod &= ~MOD_BLCP_MASK;
mod &= ~MOD_BLC_MASK;
switch (blc) {
case 8:
mod |= MOD_BLCP_8BIT;
mod |= MOD_BLC_8BIT;
break;
case 16:
mod |= MOD_BLCP_16BIT;
mod |= MOD_BLC_16BIT;
break;
case 24:
mod |= MOD_BLCP_24BIT;
mod |= MOD_BLC_24BIT;
break;
default:
debug("%s: Invalid sample size input [0x%x]\n",
__func__, blc);
return -ERANGE;
}
writel(mod, &i2s_reg->mod);
return 0;
}
int i2s_transfer_tx_data(struct i2s_uc_priv *pi2s_tx, void *data,
uint data_size)
{
struct i2s_reg *i2s_reg = (struct i2s_reg *)pi2s_tx->base_address;
u32 *ptr;
int i;
int start;
if (data_size < FIFO_LENGTH) {
debug("%s : Invalid data size\n", __func__);
return -ENODATA; /* invalid pcm data size */
}
/* fill the tx buffer before stating the tx transmit */
for (i = 0, ptr = data; i < FIFO_LENGTH; i++)
writel(*ptr++, &i2s_reg->txd);
data_size -= sizeof(*ptr) * FIFO_LENGTH;
i2s_txctrl(i2s_reg, I2S_TX_ON);
while (data_size > 0) {
start = get_timer(0);
if (!(CON_TXFIFO_FULL & (readl(&i2s_reg->con)))) {
writel(*ptr++, &i2s_reg->txd);
data_size -= sizeof(*ptr);
} else {
if (get_timer(start) > TIMEOUT_I2S_TX) {
i2s_txctrl(i2s_reg, I2S_TX_OFF);
debug("%s: I2S Transfer Timeout\n", __func__);
return -ETIMEDOUT;
}
}
}
i2s_txctrl(i2s_reg, I2S_TX_OFF);
return 0;
}
static int i2s_tx_init(struct i2s_uc_priv *pi2s_tx)
{
int ret;
struct i2s_reg *i2s_reg = (struct i2s_reg *)pi2s_tx->base_address;
if (pi2s_tx->id == 0) {
/* Initialize GPIO for I2S-0 */
exynos_pinmux_config(PERIPH_ID_I2S0, 0);
/* Set EPLL Clock */
ret = set_epll_clk(pi2s_tx->samplingrate * pi2s_tx->rfs * 4);
} else if (pi2s_tx->id == 1) {
/* Initialize GPIO for I2S-1 */
exynos_pinmux_config(PERIPH_ID_I2S1, 0);
/* Set EPLL Clock */
ret = set_epll_clk(pi2s_tx->audio_pll_clk);
} else {
debug("%s: unsupported i2s-%d bus\n", __func__, pi2s_tx->id);
return -ERANGE;
}
if (ret) {
debug("%s: epll clock set rate failed\n", __func__);
return ret;
}
/* Select Clk Source for Audio 0 or 1 */
ret = set_i2s_clk_source(pi2s_tx->id);
if (ret) {
debug("%s: unsupported clock for i2s-%d\n", __func__,
pi2s_tx->id);
return ret;
}
if (pi2s_tx->id == 0) {
/*Reset the i2s module */
writel(CON_RESET, &i2s_reg->con);
writel(MOD_OP_CLK | MOD_RCLKSRC, &i2s_reg->mod);
/* set i2s prescaler */
writel(PSREN | PSVAL, &i2s_reg->psr);
} else {
/* Set Prescaler to get MCLK */
ret = set_i2s_clk_prescaler(pi2s_tx->audio_pll_clk,
(pi2s_tx->samplingrate * (pi2s_tx->rfs)),
pi2s_tx->id);
}
if (ret) {
debug("%s: unsupported prescalar for i2s-%d\n", __func__,
pi2s_tx->id);
return ret;
}
/* Configure I2s format */
ret = i2s_set_fmt(i2s_reg, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret == 0) {
i2s_set_lr_framesize(i2s_reg, pi2s_tx->rfs);
ret = i2s_set_samplesize(i2s_reg, pi2s_tx->bitspersample);
if (ret != 0) {
debug("%s:set sample rate failed\n", __func__);
return ret;
}
i2s_set_bitclk_framesize(i2s_reg, pi2s_tx->bfs);
/* disable i2s transfer flag and flush the fifo */
i2s_txctrl(i2s_reg, I2S_TX_OFF);
i2s_fifo(i2s_reg, FIC_TXFLUSH);
} else {
debug("%s: failed\n", __func__);
}
return ret;
}
static int samsung_i2s_tx_data(struct udevice *dev, void *data, uint data_size)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
return i2s_transfer_tx_data(priv, data, data_size);
}
static int samsung_i2s_probe(struct udevice *dev)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
return i2s_tx_init(priv);
}
static int samsung_i2s_ofdata_to_platdata(struct udevice *dev)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
ulong base;
/*
* 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 = dev_read_addr(dev);
if (base == FDT_ADDR_T_NONE) {
debug("%s: Missing i2s base\n", __func__);
return -EINVAL;
}
priv->base_address = base;
if (dev_read_u32u(dev, "samsung,i2s-epll-clock-frequency",
&priv->audio_pll_clk))
goto err;
debug("audio_pll_clk = %d\n", priv->audio_pll_clk);
if (dev_read_u32u(dev, "samsung,i2s-sampling-rate",
&priv->samplingrate))
goto err;
debug("samplingrate = %d\n", priv->samplingrate);
if (dev_read_u32u(dev, "samsung,i2s-bits-per-sample",
&priv->bitspersample))
goto err;
debug("bitspersample = %d\n", priv->bitspersample);
if (dev_read_u32u(dev, "samsung,i2s-channels", &priv->channels))
goto err;
debug("channels = %d\n", priv->channels);
if (dev_read_u32u(dev, "samsung,i2s-lr-clk-framesize", &priv->rfs))
goto err;
debug("rfs = %d\n", priv->rfs);
if (dev_read_u32u(dev, "samsung,i2s-bit-clk-framesize", &priv->bfs))
goto err;
debug("bfs = %d\n", priv->bfs);
if (dev_read_u32u(dev, "samsung,i2s-id", &priv->id))
goto err;
debug("id = %d\n", priv->id);
return 0;
err:
debug("fail to get sound i2s node properties\n");
return -EINVAL;
}
static const struct i2s_ops samsung_i2s_ops = {
.tx_data = samsung_i2s_tx_data,
};
static const struct udevice_id samsung_i2s_ids[] = {
{ .compatible = "samsung,s5pv210-i2s" },
{ }
};
U_BOOT_DRIVER(samsung_i2s) = {
.name = "samsung_i2s",
.id = UCLASS_I2S,
.of_match = samsung_i2s_ids,
.probe = samsung_i2s_probe,
.ofdata_to_platdata = samsung_i2s_ofdata_to_platdata,
.ops = &samsung_i2s_ops,
};
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google, LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
#include <asm/gpio.h>
#include <asm/arch/power.h>
static int samsung_sound_setup(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct i2s_uc_priv *i2c_priv = dev_get_uclass_priv(uc_priv->i2s);
int ret;
if (uc_priv->setup_done)
return -EALREADY;
ret = audio_codec_set_params(uc_priv->codec, i2c_priv->id,
i2c_priv->samplingrate,
i2c_priv->samplingrate * i2c_priv->rfs,
i2c_priv->bitspersample,
i2c_priv->channels);
if (ret)
return ret;
uc_priv->setup_done = true;
return 0;
}
static int samsung_sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
return i2s_tx_data(uc_priv->i2s, data, data_size);
}
static int samsung_sound_probe(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct ofnode_phandle_args args;
struct gpio_desc en_gpio;
ofnode node;
int ret;
ret = gpio_request_by_name(dev, "codec-enable-gpio", 0, &en_gpio,
GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
/* Turn on the GPIO which connects to the codec's "enable" line. */
if (!ret)
gpio_set_pull(gpio_get_number(&en_gpio), S5P_GPIO_PULL_NONE);
ret = uclass_get_device_by_phandle(UCLASS_AUDIO_CODEC, dev,
"samsung,audio-codec",
&uc_priv->codec);
if (ret) {
debug("Failed to probe audio codec\n");
return ret;
}
node = ofnode_find_subnode(dev_ofnode(dev), "cpu");
if (!ofnode_valid(node)) {
debug("Failed to find /cpu subnode\n");
return -EINVAL;
}
ret = ofnode_parse_phandle_with_args(node, "sound-dai",
"#sound-dai-cells", 0, 0, &args);
if (ret) {
debug("Cannot find phandle: %d\n", ret);
return ret;
}
ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s);
if (ret) {
debug("Cannot find i2s: %d\n", ret);
return ret;
}
debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name,
uc_priv->codec->name, uc_priv->i2s->name);
/* Enable codec clock */
set_xclkout();
return 0;
}
static const struct sound_ops samsung_sound_ops = {
.setup = samsung_sound_setup,
.play = samsung_sound_play,
};
static const struct udevice_id samsung_sound_ids[] = {
{ .compatible = "google,snow-audio-max98095" },
{ .compatible = "google,spring-audio-max98088" },
{ .compatible = "samsung,smdk5420-audio-wm8994" },
{ .compatible = "google,peach-audio-max98090" },
{ }
};
U_BOOT_DRIVER(samsung_sound) = {
.name = "samsung_sound",
.id = UCLASS_SOUND,
.of_match = samsung_sound_ids,
.probe = samsung_sound_probe,
.ops = &samsung_sound_ops,
};
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2013 Google, Inc
*/
#define LOG_CATEGORY UCLASS_SOUND
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
#include <asm/sdl.h>
struct sandbox_codec_priv {
int interface;
int rate;
int mclk_freq;
int bits_per_sample;
uint channels;
};
struct sandbox_i2s_priv {
int sum; /* Use to sum the provided audio data */
bool silent; /* Sound is silent, don't use SDL */
};
struct sandbox_sound_priv {
int setup_called;
int sum; /* Use to sum the provided audio data */
bool allow_beep; /* true to allow the start_beep() interface */
int frequency_hz; /* Beep frequency if active, else 0 */
};
void sandbox_get_codec_params(struct udevice *dev, int *interfacep, int *ratep,
int *mclk_freqp, int *bits_per_samplep,
uint *channelsp)
{
struct sandbox_codec_priv *priv = dev_get_priv(dev);
*interfacep = priv->interface;
*ratep = priv->rate;
*mclk_freqp = priv->mclk_freq;
*bits_per_samplep = priv->bits_per_sample;
*channelsp = priv->channels;
}
int sandbox_get_i2s_sum(struct udevice *dev)
{
struct sandbox_i2s_priv *priv = dev_get_priv(dev);
return priv->sum;
}
int sandbox_get_setup_called(struct udevice *dev)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
return priv->setup_called;
}
int sandbox_get_sound_sum(struct udevice *dev)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
return priv->sum;
}
void sandbox_set_allow_beep(struct udevice *dev, bool allow)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
priv->allow_beep = allow;
}
int sandbox_get_beep_frequency(struct udevice *dev)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
return priv->frequency_hz;
}
static int sandbox_codec_set_params(struct udevice *dev, int interface,
int rate, int mclk_freq,
int bits_per_sample, uint channels)
{
struct sandbox_codec_priv *priv = dev_get_priv(dev);
priv->interface = interface;
priv->rate = rate;
priv->mclk_freq = mclk_freq;
priv->bits_per_sample = bits_per_sample;
priv->channels = channels;
return 0;
}
static int sandbox_i2s_tx_data(struct udevice *dev, void *data,
uint data_size)
{
struct sandbox_i2s_priv *priv = dev_get_priv(dev);
int i;
for (i = 0; i < data_size; i++)
priv->sum += ((uint8_t *)data)[i];
if (!priv->silent) {
int ret;
ret = sandbox_sdl_sound_play(data, data_size);
if (ret)
return ret;
}
return 0;
}
static int sandbox_i2s_probe(struct udevice *dev)
{
struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct sandbox_i2s_priv *priv = dev_get_priv(dev);
/* Use hard-coded values here */
uc_priv->rfs = 256;
uc_priv->bfs = 32;
uc_priv->audio_pll_clk = 192000000;
uc_priv->samplingrate = 48000;
uc_priv->bitspersample = 16;
uc_priv->channels = 2;
uc_priv->id = 1;
priv->silent = dev_read_bool(dev, "sandbox,silent");
if (priv->silent) {
log_warning("Sound is silenced\n");
} else if (sandbox_sdl_sound_init(uc_priv->samplingrate,
uc_priv->channels)) {
/* Ignore any error here - we'll just have no sound */
priv->silent = true;
}
return 0;
}
static int sandbox_sound_setup(struct udevice *dev)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
priv->setup_called++;
return 0;
}
static int sandbox_sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct sandbox_sound_priv *priv = dev_get_priv(dev);
int i;
for (i = 0; i < data_size; i++)
priv->sum += ((uint8_t *)data)[i];
return i2s_tx_data(uc_priv->i2s, data, data_size);
}
int sandbox_sound_start_beep(struct udevice *dev, int frequency_hz)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
if (!priv->allow_beep)
return -ENOSYS;
priv->frequency_hz = frequency_hz;
return 0;
}
int sandbox_sound_stop_beep(struct udevice *dev)
{
struct sandbox_sound_priv *priv = dev_get_priv(dev);
if (!priv->allow_beep)
return -ENOSYS;
priv->frequency_hz = 0;
return 0;
}
static int sandbox_sound_probe(struct udevice *dev)
{
return sound_find_codec_i2s(dev);
}
static const struct audio_codec_ops sandbox_codec_ops = {
.set_params = sandbox_codec_set_params,
};
static const struct udevice_id sandbox_codec_ids[] = {
{ .compatible = "sandbox,audio-codec" },
{ }
};
U_BOOT_DRIVER(sandbox_codec) = {
.name = "sandbox_codec",
.id = UCLASS_AUDIO_CODEC,
.of_match = sandbox_codec_ids,
.ops = &sandbox_codec_ops,
.priv_auto_alloc_size = sizeof(struct sandbox_codec_priv),
};
static const struct i2s_ops sandbox_i2s_ops = {
.tx_data = sandbox_i2s_tx_data,
};
static const struct udevice_id sandbox_i2s_ids[] = {
{ .compatible = "sandbox,i2s" },
{ }
};
U_BOOT_DRIVER(sandbox_i2s) = {
.name = "sandbox_i2s",
.id = UCLASS_I2S,
.of_match = sandbox_i2s_ids,
.ops = &sandbox_i2s_ops,
.probe = sandbox_i2s_probe,
.priv_auto_alloc_size = sizeof(struct sandbox_i2s_priv),
};
static const struct sound_ops sandbox_sound_ops = {
.setup = sandbox_sound_setup,
.play = sandbox_sound_play,
.start_beep = sandbox_sound_start_beep,
.stop_beep = sandbox_sound_stop_beep,
};
static const struct udevice_id sandbox_sound_ids[] = {
{ .compatible = "sandbox,sound" },
{ }
};
U_BOOT_DRIVER(sandbox_sound) = {
.name = "sandbox_sound",
.id = UCLASS_SOUND,
.of_match = sandbox_sound_ids,
.ops = &sandbox_sound_ops,
.priv_auto_alloc_size = sizeof(struct sandbox_sound_priv),
.probe = sandbox_sound_probe,
};
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <sound.h>
#define SOUND_BITS_IN_BYTE 8
int sound_setup(struct udevice *dev)
{
struct sound_ops *ops = sound_get_ops(dev);
if (!ops->setup)
return 0;
return ops->setup(dev);
}
int sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_ops *ops = sound_get_ops(dev);
if (!ops->play)
return -ENOSYS;
return ops->play(dev, data, data_size);
}
int sound_start_beep(struct udevice *dev, int frequency_hz)
{
struct sound_ops *ops = sound_get_ops(dev);
if (!ops->start_beep)
return -ENOSYS;
return ops->start_beep(dev, frequency_hz);
}
int sound_stop_beep(struct udevice *dev)
{
struct sound_ops *ops = sound_get_ops(dev);
if (!ops->stop_beep)
return -ENOSYS;
return ops->stop_beep(dev);
}
int sound_beep(struct udevice *dev, int msecs, int frequency_hz)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct i2s_uc_priv *i2s_uc_priv;
unsigned short *data;
uint data_size;
int ret;
ret = sound_setup(dev);
if (ret && ret != -EALREADY)
return ret;
/* Try using the beep interface if available */
ret = sound_start_beep(dev, frequency_hz);
if (ret != -ENOSYS) {
if (ret)
return ret;
mdelay(msecs);
ret = sound_stop_beep(dev);
return ret;
}
/* Buffer length computation */
i2s_uc_priv = dev_get_uclass_priv(uc_priv->i2s);
data_size = i2s_uc_priv->samplingrate * i2s_uc_priv->channels;
data_size *= (i2s_uc_priv->bitspersample / SOUND_BITS_IN_BYTE);
data = malloc(data_size);
if (!data) {
debug("%s: malloc failed\n", __func__);
return -ENOMEM;
}
sound_create_square_wave(i2s_uc_priv->samplingrate, data, data_size,
frequency_hz, i2s_uc_priv->channels);
while (msecs >= 1000) {
ret = sound_play(dev, data, data_size);
msecs -= 1000;
}
if (msecs) {
unsigned long size =
(data_size * msecs) / (sizeof(int) * 1000);
ret = sound_play(dev, data, size);
}
free(data);
return ret;
}
int sound_find_codec_i2s(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct ofnode_phandle_args args;
ofnode node;
int ret;
/* First the codec */
node = ofnode_find_subnode(dev_ofnode(dev), "codec");
if (!ofnode_valid(node)) {
debug("Failed to find /cpu subnode\n");
return -EINVAL;
}
ret = ofnode_parse_phandle_with_args(node, "sound-dai",
"#sound-dai-cells", 0, 0, &args);
if (ret) {
debug("Cannot find phandle: %d\n", ret);
return ret;
}
ret = uclass_get_device_by_ofnode(UCLASS_AUDIO_CODEC, args.node,
&uc_priv->codec);
if (ret) {
debug("Cannot find codec: %d\n", ret);
return ret;
}
/* Now the i2s */
node = ofnode_find_subnode(dev_ofnode(dev), "cpu");
if (!ofnode_valid(node)) {
debug("Failed to find /cpu subnode\n");
return -EINVAL;
}
ret = ofnode_parse_phandle_with_args(node, "sound-dai",
"#sound-dai-cells", 0, 0, &args);
if (ret) {
debug("Cannot find phandle: %d\n", ret);
return ret;
}
ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s);
if (ret) {
debug("Cannot find i2s: %d\n", ret);
return ret;
}
debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name,
uc_priv->codec->name, uc_priv->i2s->name);
return 0;
}
UCLASS_DRIVER(sound) = {
.id = UCLASS_SOUND,
.name = "sound",
.per_device_auto_alloc_size = sizeof(struct sound_uc_priv),
};
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 Samsung Electronics
* R. Chandrasekar <rcsekar@samsung.com>
*/
#include <common.h>
#include <sound.h>
void sound_create_square_wave(uint sample_rate, unsigned short *data, int size,
uint freq, uint channels)
{
const unsigned short amplitude = 16000; /* between 1 and 32767 */
const int period = freq ? sample_rate / 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, j;
for (i = 0; size && i < half; i++) {
size -= 2;
for (j = 0; j < channels; j++)
*data++ = amplitude;
}
for (i = 0; size && i < period - half; i++) {
size -= 2;
for (j = 0; j < channels; j++)
*data++ = -amplitude;
}
}
}
@@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-2.0+159
/*
* Take from dc tegra_ahub.c
*
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY UCLASS_MISC
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <misc.h>
#include <asm/io.h>
#include <asm/arch-tegra/tegra_ahub.h>
#include <asm/arch-tegra/tegra_i2s.h>
#include "tegra_i2s_priv.h"
struct tegra_ahub_priv {
struct apbif_regs *apbif_regs;
struct xbar_regs *xbar_regs;
u32 full_mask;
int capacity_words; /* FIFO capacity in words */
/*
* This is unset intially, but is set by tegra_ahub_ioctl() called
* from the misc_ioctl() in tegra_sound_probe()
*/
struct udevice *i2s;
struct udevice *dma;
};
static int tegra_ahub_xbar_enable_i2s(struct xbar_regs *regs, int i2s_id)
{
/*
* Enables I2S as the receiver of APBIF by writing APBIF_TX0 (0x01) to
* the rx0 register
*/
switch (i2s_id) {
case 0:
writel(1, &regs->i2s0_rx0);
break;
case 1:
writel(1, &regs->i2s1_rx0);
break;
case 2:
writel(1, &regs->i2s2_rx0);
break;
case 3:
writel(1, &regs->i2s3_rx0);
break;
case 4:
writel(1, &regs->i2s4_rx0);
break;
default:
log_err("Invalid I2S component id: %d\n", i2s_id);
return -EINVAL;
}
return 0;
}
static int tegra_ahub_apbif_is_full(struct udevice *dev)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
return readl(&priv->apbif_regs->apbdma_live_stat) & priv->full_mask;
}
/**
* tegra_ahub_wait_for_space() - Wait for space in the FIFO
*
* @return 0 if OK, -ETIMEDOUT if no space was available in time
*/
static int tegra_ahub_wait_for_space(struct udevice *dev)
{
int i = 100000;
ulong start;
/* Busy-wait initially, since this should take almost no time */
while (i--) {
if (!tegra_ahub_apbif_is_full(dev))
return 0;
}
/* Failed, so do a slower loop for 100ms */
start = get_timer(0);
while (tegra_ahub_apbif_is_full(dev)) {
if (get_timer(start) > 100)
return -ETIMEDOUT;
}
return 0;
}
static int tegra_ahub_apbif_send(struct udevice *dev, int offset,
const void *buf, int len)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
const u32 *data = (const u32 *)buf;
ssize_t written = 0;
if (len % sizeof(*data)) {
log_err("Data size (%zd) must be aligned to %zd.\n", len,
sizeof(*data));
return -EFAULT;
}
while (written < len) {
int ret = tegra_ahub_wait_for_space(dev);
if (ret)
return ret;
writel(*data++, &priv->apbif_regs->channel0_txfifo);
written += sizeof(*data);
}
return written;
}
static void tegra_ahub_apbif_set_cif(struct udevice *dev, u32 value)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
writel(value, &priv->apbif_regs->channel0_cif_tx0_ctrl);
}
static void tegra_ahub_apbif_enable_channel0(struct udevice *dev,
int fifo_threshold)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
u32 ctrl = TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_EN |
TEGRA_AHUB_CHANNEL_CTRL_TX_PACK_16 |
TEGRA_AHUB_CHANNEL_CTRL_TX_EN;
fifo_threshold--; /* fifo_threshold starts from 1 */
ctrl |= (fifo_threshold << TEGRA_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT);
writel(ctrl, &priv->apbif_regs->channel0_ctrl);
}
static u32 tegra_ahub_get_cif(bool is_receive, uint channels,
uint bits_per_sample, uint fifo_threshold)
{
uint audio_bits = (bits_per_sample >> 2) - 1;
u32 val;
channels--; /* Channels in CIF starts from 1 */
fifo_threshold--; /* FIFO threshold starts from 1 */
/* Assume input and output are always using same channel / bits */
val = channels << TEGRA_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT |
channels << TEGRA_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT |
audio_bits << TEGRA_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT |
audio_bits << TEGRA_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT |
fifo_threshold << TEGRA_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT |
(is_receive ? TEGRA_AUDIOCIF_DIRECTION_RX <<
TEGRA_AUDIOCIF_CTRL_DIRECTION_SHIFT : 0);
return val;
}
static int tegra_ahub_enable(struct udevice *dev)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
struct i2s_uc_priv *uc_priv = dev_get_uclass_priv(priv->i2s);
u32 cif_ctrl = 0;
int ret;
/* We use APBIF channel0 as a sender */
priv->full_mask = TEGRA_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL;
priv->capacity_words = 8;
/*
* FIFO is inactive until (fifo_threshold) of words are sent. For
* better performance, we want to set it to half of capacity.
*/
u32 fifo_threshold = priv->capacity_words / 2;
/*
* Setup audio client interface (ACIF): APBIF (channel0) as sender and
* I2S as receiver
*/
cif_ctrl = tegra_ahub_get_cif(true, uc_priv->channels,
uc_priv->bitspersample, fifo_threshold);
tegra_i2s_set_cif_tx_ctrl(priv->i2s, cif_ctrl);
cif_ctrl = tegra_ahub_get_cif(false, uc_priv->channels,
uc_priv->bitspersample, fifo_threshold);
tegra_ahub_apbif_set_cif(dev, cif_ctrl);
tegra_ahub_apbif_enable_channel0(dev, fifo_threshold);
ret = tegra_ahub_xbar_enable_i2s(priv->xbar_regs, uc_priv->id);
if (ret)
return ret;
log_debug("ahub: channels=%d, bitspersample=%d, cif_ctrl=%x, fifo_threshold=%d, id=%d\n",
uc_priv->channels, uc_priv->bitspersample, cif_ctrl,
fifo_threshold, uc_priv->id);
return 0;
}
static int tegra_ahub_ioctl(struct udevice *dev, unsigned long request,
void *buf)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
if (request != AHUB_MISCOP_SET_I2S)
return -ENOSYS;
priv->i2s = *(struct udevice **)buf;
log_debug("i2s set to '%s'\n", priv->i2s->name);
return tegra_ahub_enable(dev);
}
static int tegra_ahub_probe(struct udevice *dev)
{
struct tegra_ahub_priv *priv = dev_get_priv(dev);
ulong addr;
addr = dev_read_addr_index(dev, 0);
if (addr == FDT_ADDR_T_NONE) {
log_debug("Invalid apbif address\n");
return -EINVAL;
}
priv->apbif_regs = (struct apbif_regs *)addr;
addr = dev_read_addr_index(dev, 1);
if (addr == FDT_ADDR_T_NONE) {
log_debug("Invalid xbar address\n");
return -EINVAL;
}
priv->xbar_regs = (struct xbar_regs *)addr;
log_debug("ahub apbif_regs=%p, xbar_regs=%p\n", priv->apbif_regs,
priv->xbar_regs);
return 0;
}
static struct misc_ops tegra_ahub_ops = {
.write = tegra_ahub_apbif_send,
.ioctl = tegra_ahub_ioctl,
};
static const struct udevice_id tegra_ahub_ids[] = {
{ .compatible = "nvidia,tegra124-ahub" },
{ }
};
U_BOOT_DRIVER(tegra_ahub) = {
.name = "tegra_ahub",
.id = UCLASS_MISC,
.of_match = tegra_ahub_ids,
.ops = &tegra_ahub_ops,
.probe = tegra_ahub_probe,
.priv_auto_alloc_size = sizeof(struct tegra_ahub_priv),
};
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY UCLASS_I2S
#define LOG_DEBUG
#include <common.h>
#include <dm.h>
#include <i2s.h>
#include <misc.h>
#include <sound.h>
#include <asm/io.h>
#include <asm/arch-tegra/tegra_i2s.h>
#include "tegra_i2s_priv.h"
int tegra_i2s_set_cif_tx_ctrl(struct udevice *dev, u32 value)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
struct i2s_ctlr *regs = (struct i2s_ctlr *)priv->base_address;
writel(value, &regs->cif_tx_ctrl);
return 0;
}
static void tegra_i2s_transmit_enable(struct i2s_ctlr *regs, int on)
{
clrsetbits_le32(&regs->ctrl, I2S_CTRL_XFER_EN_TX,
on ? I2S_CTRL_XFER_EN_TX : 0);
}
static int i2s_tx_init(struct i2s_uc_priv *pi2s_tx)
{
struct i2s_ctlr *regs = (struct i2s_ctlr *)pi2s_tx->base_address;
u32 audio_bits = (pi2s_tx->bitspersample >> 2) - 1;
u32 ctrl = readl(&regs->ctrl);
/* Set format to LRCK / Left Low */
ctrl &= ~(I2S_CTRL_FRAME_FORMAT_MASK | I2S_CTRL_LRCK_MASK);
ctrl |= I2S_CTRL_FRAME_FORMAT_LRCK;
ctrl |= I2S_CTRL_LRCK_L_LOW;
/* Disable all transmission until we are ready to transfer */
ctrl &= ~(I2S_CTRL_XFER_EN_TX | I2S_CTRL_XFER_EN_RX);
/* Serve as master */
ctrl |= I2S_CTRL_MASTER_ENABLE;
/* Configure audio bits size */
ctrl &= ~I2S_CTRL_BIT_SIZE_MASK;
ctrl |= audio_bits << I2S_CTRL_BIT_SIZE_SHIFT;
writel(ctrl, &regs->ctrl);
/* Timing in LRCK mode: */
writel(pi2s_tx->bitspersample, &regs->timing);
/* I2S mode has [TX/RX]_DATA_OFFSET both set to 1 */
writel(((1 << I2S_OFFSET_RX_DATA_OFFSET_SHIFT) |
(1 << I2S_OFFSET_TX_DATA_OFFSET_SHIFT)), &regs->offset);
/* FSYNC_WIDTH = 2 clocks wide, TOTAL_SLOTS = 2 slots per fsync */
writel((2 - 1) << I2S_CH_CTRL_FSYNC_WIDTH_SHIFT, &regs->ch_ctrl);
return 0;
}
static int tegra_i2s_tx_data(struct udevice *dev, void *data, uint data_size)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
struct i2s_ctlr *regs = (struct i2s_ctlr *)priv->base_address;
int ret;
tegra_i2s_transmit_enable(regs, 1);
ret = misc_write(dev_get_parent(dev), 0, data, data_size);
tegra_i2s_transmit_enable(regs, 0);
if (ret < 0)
return ret;
else if (ret < data_size)
return -EIO;
return 0;
}
static int tegra_i2s_probe(struct udevice *dev)
{
struct i2s_uc_priv *priv = dev_get_uclass_priv(dev);
ulong base;
base = dev_read_addr(dev);
if (base == FDT_ADDR_T_NONE) {
debug("%s: Missing i2s base\n", __func__);
return -EINVAL;
}
priv->base_address = base;
priv->id = 1;
priv->audio_pll_clk = 4800000;
priv->samplingrate = 48000;
priv->bitspersample = 16;
priv->channels = 2;
priv->rfs = 256;
priv->bfs = 32;
return i2s_tx_init(priv);
}
static const struct i2s_ops tegra_i2s_ops = {
.tx_data = tegra_i2s_tx_data,
};
static const struct udevice_id tegra_i2s_ids[] = {
{ .compatible = "nvidia,tegra124-i2s" },
{ }
};
U_BOOT_DRIVER(tegra_i2s) = {
.name = "tegra_i2s",
.id = UCLASS_I2S,
.of_match = tegra_i2s_ids,
.probe = tegra_i2s_probe,
.ops = &tegra_i2s_ops,
};
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2018 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#ifndef __TEGRA_I2S_PRIV_H
#define __TEGRA_I2S_PRIV_H
enum {
/* Set i2s device (in buf) */
AHUB_MISCOP_SET_I2S,
};
/*
* tegra_i2s_set_cif_tx_ctrl() - Set the I2C port to send to
*
* The CIF is not really part of I2S -- it's for Audio Hub to control
* the interface between I2S and Audio Hub. However since it's put in
* the I2S registers domain instead of the Audio Hub, we need to export
* this as a function.
*
* @dev: I2S device
* @value: Value to write to CIF_TX_CTRL register
* @return 0
*/
int tegra_i2s_set_cif_tx_ctrl(struct udevice *dev, u32 value);
#endif
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 Google, LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY UCLASS_I2S
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <i2s.h>
#include <misc.h>
#include <sound.h>
#include <asm/gpio.h>
#include "tegra_i2s_priv.h"
static int tegra_sound_setup(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct i2s_uc_priv *i2c_priv = dev_get_uclass_priv(uc_priv->i2s);
int ret;
if (uc_priv->setup_done)
return -EALREADY;
ret = audio_codec_set_params(uc_priv->codec, i2c_priv->id,
i2c_priv->samplingrate,
i2c_priv->samplingrate * i2c_priv->rfs,
i2c_priv->bitspersample,
i2c_priv->channels);
if (ret)
return ret;
uc_priv->setup_done = true;
return 0;
}
static int tegra_sound_play(struct udevice *dev, void *data, uint data_size)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
return i2s_tx_data(uc_priv->i2s, data, data_size);
}
static int tegra_sound_probe(struct udevice *dev)
{
struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct gpio_desc en_gpio;
struct udevice *ahub;
int ret;
ret = gpio_request_by_name(dev, "codec-enable-gpio", 0, &en_gpio,
GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
ret = uclass_get_device_by_phandle(UCLASS_AUDIO_CODEC, dev,
"nvidia,audio-codec",
&uc_priv->codec);
if (ret) {
log_debug("Failed to probe audio codec\n");
return ret;
}
ret = uclass_get_device_by_phandle(UCLASS_I2S, dev,
"nvidia,i2s-controller",
&uc_priv->i2s);
if (ret) {
log_debug("Cannot find i2s: %d\n", ret);
return ret;
}
/* Set up the audio hub, telling it the currect i2s to use */
ahub = dev_get_parent(uc_priv->i2s);
ret = misc_ioctl(ahub, AHUB_MISCOP_SET_I2S, &uc_priv->i2s);
if (ret) {
log_debug("Cannot set i2c: %d\n", ret);
return ret;
}
log_debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name,
uc_priv->codec->name, uc_priv->i2s->name);
return 0;
}
static const struct sound_ops tegra_sound_ops = {
.setup = tegra_sound_setup,
.play = tegra_sound_play,
};
static const struct udevice_id tegra_sound_ids[] = {
{ .compatible = "nvidia,tegra-audio-max98090-nyan-big" },
{ }
};
U_BOOT_DRIVER(tegra_sound) = {
.name = "tegra_sound",
.id = UCLASS_SOUND,
.of_match = tegra_sound_ids,
.probe = tegra_sound_probe,
.ops = &tegra_sound_ops,
};
@@ -0,0 +1,876 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 Samsung Electronics
* R. Chandrasekar <rcsekar@samsung.com>
*/
#include <common.h>
#include <audio_codec.h>
#include <dm.h>
#include <div64.h>
#include <fdtdec.h>
#include <i2c.h>
#include <i2s.h>
#include <sound.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/clk.h>
#include <asm/arch/cpu.h>
#include <asm/arch/sound.h>
#include "wm8994.h"
#include "wm8994_registers.h"
/* defines for wm8994 system clock selection */
#define SEL_MCLK1 0x00
#define SEL_MCLK2 0x08
#define SEL_FLL1 0x10
#define SEL_FLL2 0x18
/* fll config to configure fll */
struct wm8994_fll_config {
int src; /* Source */
int in; /* Input frequency in Hz */
int out; /* output frequency in Hz */
};
/* codec private data */
struct wm8994_priv {
enum wm8994_type type; /* codec type of wolfson */
int revision; /* Revision */
int sysclk[WM8994_MAX_AIF]; /* System clock frequency in Hz */
int mclk[WM8994_MAX_AIF]; /* master clock frequency in Hz */
int aifclk[WM8994_MAX_AIF]; /* audio interface clock in Hz */
struct wm8994_fll_config fll[2]; /* fll config to configure fll */
struct udevice *dev;
};
/* wm 8994 supported sampling rate values */
static unsigned int src_rate[] = {
8000, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 88200, 96000
};
/* op clock divisions */
static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
/* lr clock frame size ratio */
static int fs_ratios[] = {
64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536
};
/* bit clock divisors */
static int bclk_divs[] = {
10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480,
640, 880, 960, 1280, 1760, 1920
};
/*
* Writes value to a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be write
* @param data data to be writen to the above registor
*
* @return int value 1 for change, 0 for no change or negative error code.
*/
static int wm8994_i2c_write(struct wm8994_priv *priv, unsigned int reg,
unsigned short data)
{
unsigned char val[2];
val[0] = (unsigned char)((data >> 8) & 0xff);
val[1] = (unsigned char)(data & 0xff);
debug("Write Addr : 0x%04X, Data : 0x%04X\n", reg, data);
return dm_i2c_write(priv->dev, reg, val, 2);
}
/*
* Read a value from a device register through i2c
*
* @param priv Private data for driver
* @param reg reg number to be read
* @param data address of read data to be stored
*
* @return int value 0 for success, -1 in case of error.
*/
static unsigned int wm8994_i2c_read(struct wm8994_priv *priv, unsigned int reg,
unsigned short *data)
{
unsigned char val[2];
int ret;
ret = dm_i2c_read(priv->dev, reg, val, 1);
if (ret != 0) {
debug("%s: Error while reading register %#04x\n",
__func__, reg);
return -1;
}
*data = val[0];
*data <<= 8;
*data |= val[1];
return 0;
}
/*
* update device register bits through i2c
*
* @param priv Private data for driver
* @param reg codec register
* @param mask register mask
* @param value new value
*
* @return int value 1 if change in the register value,
* 0 for no change or negative error code.
*/
static int wm8994_bic_or(struct wm8994_priv *priv, unsigned int reg,
unsigned short mask, unsigned short value)
{
int change , ret = 0;
unsigned short old, new;
if (wm8994_i2c_read(priv, reg, &old) != 0)
return -1;
new = (old & ~mask) | (value & mask);
change = (old != new) ? 1 : 0;
if (change)
ret = wm8994_i2c_write(priv, reg, new);
if (ret < 0)
return ret;
return change;
}
/*
* Sets i2s set format
*
* @param priv wm8994 information
* @param aif_id Interface ID
* @param fmt i2S format
*
* @return -1 for error and 0 Success.
*/
static int wm8994_set_fmt(struct wm8994_priv *priv, int aif_id, uint fmt)
{
int ms_reg;
int aif_reg;
int ms = 0;
int aif = 0;
int aif_clk = 0;
int error = 0;
switch (aif_id) {
case 1:
ms_reg = WM8994_AIF1_MASTER_SLAVE;
aif_reg = WM8994_AIF1_CONTROL_1;
aif_clk = WM8994_AIF1_CLOCKING_1;
break;
case 2:
ms_reg = WM8994_AIF2_MASTER_SLAVE;
aif_reg = WM8994_AIF2_CONTROL_1;
aif_clk = WM8994_AIF2_CLOCKING_1;
break;
default:
debug("%s: Invalid audio interface selection\n", __func__);
return -1;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
break;
case SND_SOC_DAIFMT_CBM_CFM:
ms = WM8994_AIF1_MSTR;
break;
default:
debug("%s: Invalid i2s master selection\n", __func__);
return -1;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_B:
aif |= WM8994_AIF1_LRCLK_INV;
case SND_SOC_DAIFMT_DSP_A:
aif |= 0x18;
break;
case SND_SOC_DAIFMT_I2S:
aif |= 0x10;
break;
case SND_SOC_DAIFMT_RIGHT_J:
break;
case SND_SOC_DAIFMT_LEFT_J:
aif |= 0x8;
break;
default:
debug("%s: Invalid i2s format selection\n", __func__);
return -1;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
/* frame inversion not valid for DSP modes */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_NF:
aif |= WM8994_AIF1_BCLK_INV;
break;
default:
debug("%s: Invalid i2s frame inverse selection\n",
__func__);
return -1;
}
break;
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_LEFT_J:
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_IB_IF:
aif |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV;
break;
case SND_SOC_DAIFMT_IB_NF:
aif |= WM8994_AIF1_BCLK_INV;
break;
case SND_SOC_DAIFMT_NB_IF:
aif |= WM8994_AIF1_LRCLK_INV;
break;
default:
debug("%s: Invalid i2s clock polarity selection\n",
__func__);
return -1;
}
break;
default:
debug("%s: Invalid i2s format selection\n", __func__);
return -1;
}
error = wm8994_bic_or(priv, aif_reg, WM8994_AIF1_BCLK_INV |
WM8994_AIF1_LRCLK_INV_MASK |
WM8994_AIF1_FMT_MASK, aif);
error |= wm8994_bic_or(priv, ms_reg, WM8994_AIF1_MSTR_MASK, ms);
error |= wm8994_bic_or(priv, aif_clk, WM8994_AIF1CLK_ENA_MASK,
WM8994_AIF1CLK_ENA);
if (error < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Sets hw params FOR WM8994
*
* @param priv wm8994 information pointer
* @param aif_id Audio interface ID
* @param sampling_rate Sampling rate
* @param bits_per_sample Bits per sample
* @param Channels Channels in the given audio input
*
* @return -1 for error and 0 Success.
*/
static int wm8994_hw_params(struct wm8994_priv *priv, int aif_id,
uint sampling_rate, uint bits_per_sample,
uint channels)
{
int aif1_reg;
int aif2_reg;
int bclk_reg;
int bclk = 0;
int rate_reg;
int aif1 = 0;
int aif2 = 0;
int rate_val = 0;
int id = aif_id - 1;
int i, cur_val, best_val, bclk_rate, best;
unsigned short reg_data;
int ret = 0;
switch (aif_id) {
case 1:
aif1_reg = WM8994_AIF1_CONTROL_1;
aif2_reg = WM8994_AIF1_CONTROL_2;
bclk_reg = WM8994_AIF1_BCLK;
rate_reg = WM8994_AIF1_RATE;
break;
case 2:
aif1_reg = WM8994_AIF2_CONTROL_1;
aif2_reg = WM8994_AIF2_CONTROL_2;
bclk_reg = WM8994_AIF2_BCLK;
rate_reg = WM8994_AIF2_RATE;
break;
default:
return -1;
}
bclk_rate = sampling_rate * 32;
switch (bits_per_sample) {
case 16:
bclk_rate *= 16;
break;
case 20:
bclk_rate *= 20;
aif1 |= 0x20;
break;
case 24:
bclk_rate *= 24;
aif1 |= 0x40;
break;
case 32:
bclk_rate *= 32;
aif1 |= 0x60;
break;
default:
return -1;
}
/* Try to find an appropriate sample rate; look for an exact match. */
for (i = 0; i < ARRAY_SIZE(src_rate); i++)
if (src_rate[i] == sampling_rate)
break;
if (i == ARRAY_SIZE(src_rate)) {
debug("%s: Could not get the best matching samplingrate\n",
__func__);
return -1;
}
rate_val |= i << WM8994_AIF1_SR_SHIFT;
/* AIFCLK/fs ratio; look for a close match in either direction */
best = 0;
best_val = abs((fs_ratios[0] * sampling_rate) - priv->aifclk[id]);
for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) {
cur_val = abs(fs_ratios[i] * sampling_rate - priv->aifclk[id]);
if (cur_val >= best_val)
continue;
best = i;
best_val = cur_val;
}
rate_val |= best;
/*
* We may not get quite the right frequency if using
* approximate clocks so look for the closest match that is
* higher than the target (we need to ensure that there enough
* BCLKs to clock out the samples).
*/
best = 0;
for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
cur_val = (priv->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate;
if (cur_val < 0) /* BCLK table is sorted */
break;
best = i;
}
if (i == ARRAY_SIZE(bclk_divs)) {
debug("%s: Could not get the best matching bclk division\n",
__func__);
return -1;
}
bclk_rate = priv->aifclk[id] * 10 / bclk_divs[best];
bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT;
if (wm8994_i2c_read(priv, aif1_reg, &reg_data) != 0) {
debug("%s: AIF1 register read Failed\n", __func__);
return -1;
}
if ((channels == 1) && ((reg_data & 0x18) == 0x18))
aif2 |= WM8994_AIF1_MONO;
if (priv->aifclk[id] == 0) {
debug("%s:Audio interface clock not set\n", __func__);
return -1;
}
ret = wm8994_bic_or(priv, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
ret |= wm8994_bic_or(priv, aif2_reg, WM8994_AIF1_MONO, aif2);
ret |= wm8994_bic_or(priv, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK,
bclk);
ret |= wm8994_bic_or(priv, rate_reg, WM8994_AIF1_SR_MASK |
WM8994_AIF1CLK_RATE_MASK, rate_val);
debug("rate vale = %x , bclk val= %x\n", rate_val, bclk);
if (ret < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Configures Audio interface Clock
*
* @param priv wm8994 information pointer
* @param aif Audio Interface ID
*
* @return -1 for error and 0 Success.
*/
static int configure_aif_clock(struct wm8994_priv *priv, int aif)
{
int rate;
int reg1 = 0;
int offset;
int ret;
/* AIF(1/0) register adress offset calculated */
if (aif-1)
offset = 4;
else
offset = 0;
switch (priv->sysclk[aif - 1]) {
case WM8994_SYSCLK_MCLK1:
reg1 |= SEL_MCLK1;
rate = priv->mclk[0];
break;
case WM8994_SYSCLK_MCLK2:
reg1 |= SEL_MCLK2;
rate = priv->mclk[1];
break;
case WM8994_SYSCLK_FLL1:
reg1 |= SEL_FLL1;
rate = priv->fll[0].out;
break;
case WM8994_SYSCLK_FLL2:
reg1 |= SEL_FLL2;
rate = priv->fll[1].out;
break;
default:
debug("%s: Invalid input clock selection [%d]\n",
__func__, priv->sysclk[aif - 1]);
return -1;
}
/* if input clock frequenct is more than 135Mhz then divide */
if (rate >= WM8994_MAX_INPUT_CLK_FREQ) {
rate /= 2;
reg1 |= WM8994_AIF1CLK_DIV;
}
priv->aifclk[aif - 1] = rate;
ret = wm8994_bic_or(priv, WM8994_AIF1_CLOCKING_1 + offset,
WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV,
reg1);
if (aif == WM8994_AIF1)
ret |= wm8994_bic_or(priv, WM8994_CLOCKING_1,
WM8994_AIF1DSPCLK_ENA_MASK | WM8994_SYSDSPCLK_ENA_MASK,
WM8994_AIF1DSPCLK_ENA | WM8994_SYSDSPCLK_ENA);
else if (aif == WM8994_AIF2)
ret |= wm8994_bic_or(priv, WM8994_CLOCKING_1,
WM8994_SYSCLK_SRC | WM8994_AIF2DSPCLK_ENA_MASK |
WM8994_SYSDSPCLK_ENA_MASK, WM8994_SYSCLK_SRC |
WM8994_AIF2DSPCLK_ENA | WM8994_SYSDSPCLK_ENA);
if (ret < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Configures Audio interface for the given frequency
*
* @param priv wm8994 information
* @param aif_id Audio Interface
* @param clk_id Input Clock ID
* @param freq Sampling frequency in Hz
*
* @return -1 for error and 0 success.
*/
static int wm8994_set_sysclk(struct wm8994_priv *priv, int aif_id, int clk_id,
unsigned int freq)
{
int i;
int ret = 0;
priv->sysclk[aif_id - 1] = clk_id;
switch (clk_id) {
case WM8994_SYSCLK_MCLK1:
priv->mclk[0] = freq;
if (aif_id == 2) {
ret = wm8994_bic_or(priv, WM8994_AIF1_CLOCKING_2,
WM8994_AIF2DAC_DIV_MASK, 0);
}
break;
case WM8994_SYSCLK_MCLK2:
/* TODO: Set GPIO AF */
priv->mclk[1] = freq;
break;
case WM8994_SYSCLK_FLL1:
case WM8994_SYSCLK_FLL2:
break;
case WM8994_SYSCLK_OPCLK:
/*
* Special case - a division (times 10) is given and
* no effect on main clocking.
*/
if (freq) {
for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
if (opclk_divs[i] == freq)
break;
if (i == ARRAY_SIZE(opclk_divs)) {
debug("%s frequency divisor not found\n",
__func__);
return -1;
}
ret = wm8994_bic_or(priv, WM8994_CLOCKING_2,
WM8994_OPCLK_DIV_MASK, i);
ret |= wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_2,
WM8994_OPCLK_ENA,
WM8994_OPCLK_ENA);
} else {
ret |= wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_2,
WM8994_OPCLK_ENA, 0);
}
default:
debug("%s Invalid input clock selection [%d]\n",
__func__, clk_id);
return -1;
}
ret |= configure_aif_clock(priv, aif_id);
if (ret < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Initializes Volume for AIF2 to HP path
*
* @param priv wm8994 information
* @returns -1 for error and 0 Success.
*
*/
static int wm8994_init_volume_aif2_dac1(struct wm8994_priv *priv)
{
int ret;
/* Unmute AIF2DAC */
ret = wm8994_bic_or(priv, WM8994_AIF2_DAC_FILTERS_1,
WM8994_AIF2DAC_MUTE_MASK, 0);
ret |= wm8994_bic_or(priv, WM8994_AIF2_DAC_LEFT_VOLUME,
WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACL_VOL_MASK,
WM8994_AIF2DAC_VU | 0xff);
ret |= wm8994_bic_or(priv, WM8994_AIF2_DAC_RIGHT_VOLUME,
WM8994_AIF2DAC_VU_MASK | WM8994_AIF2DACR_VOL_MASK,
WM8994_AIF2DAC_VU | 0xff);
ret |= wm8994_bic_or(priv, WM8994_DAC1_LEFT_VOLUME,
WM8994_DAC1_VU_MASK | WM8994_DAC1L_VOL_MASK |
WM8994_DAC1L_MUTE_MASK, WM8994_DAC1_VU | 0xc0);
ret |= wm8994_bic_or(priv, WM8994_DAC1_RIGHT_VOLUME,
WM8994_DAC1_VU_MASK | WM8994_DAC1R_VOL_MASK |
WM8994_DAC1R_MUTE_MASK, WM8994_DAC1_VU | 0xc0);
/* Head Phone Volume */
ret |= wm8994_i2c_write(priv, WM8994_LEFT_OUTPUT_VOLUME, 0x12D);
ret |= wm8994_i2c_write(priv, WM8994_RIGHT_OUTPUT_VOLUME, 0x12D);
if (ret < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Initializes Volume for AIF1 to HP path
*
* @param priv wm8994 information
* @returns -1 for error and 0 Success.
*
*/
static int wm8994_init_volume_aif1_dac1(struct wm8994_priv *priv)
{
int ret = 0;
/* Unmute AIF1DAC */
ret |= wm8994_i2c_write(priv, WM8994_AIF1_DAC_FILTERS_1, 0x0000);
ret |= wm8994_bic_or(priv, WM8994_DAC1_LEFT_VOLUME,
WM8994_DAC1_VU_MASK | WM8994_DAC1L_VOL_MASK |
WM8994_DAC1L_MUTE_MASK, WM8994_DAC1_VU | 0xc0);
ret |= wm8994_bic_or(priv, WM8994_DAC1_RIGHT_VOLUME,
WM8994_DAC1_VU_MASK | WM8994_DAC1R_VOL_MASK |
WM8994_DAC1R_MUTE_MASK, WM8994_DAC1_VU | 0xc0);
/* Head Phone Volume */
ret |= wm8994_i2c_write(priv, WM8994_LEFT_OUTPUT_VOLUME, 0x12D);
ret |= wm8994_i2c_write(priv, WM8994_RIGHT_OUTPUT_VOLUME, 0x12D);
if (ret < 0) {
debug("%s: codec register access error\n", __func__);
return -1;
}
return 0;
}
/*
* Intialise wm8994 codec device
*
* @param priv wm8994 information
*
* @returns -1 for error and 0 Success.
*/
static int wm8994_device_init(struct wm8994_priv *priv)
{
const char *devname;
unsigned short reg_data;
int ret;
wm8994_i2c_write(priv, WM8994_SOFTWARE_RESET, WM8994_SW_RESET);
ret = wm8994_i2c_read(priv, WM8994_SOFTWARE_RESET, &reg_data);
if (ret < 0) {
debug("Failed to read ID register\n");
return ret;
}
if (reg_data == WM8994_ID) {
devname = "WM8994";
debug("Device registered as type %d\n", priv->type);
priv->type = WM8994;
} else {
debug("Device is not a WM8994, ID is %x\n", ret);
return -ENXIO;
}
ret = wm8994_i2c_read(priv, WM8994_CHIP_REVISION, &reg_data);
if (ret < 0) {
debug("Failed to read revision register: %d\n", ret);
return ret;
}
priv->revision = reg_data;
debug("%s revision %c\n", devname, 'A' + priv->revision);
return 0;
}
static int wm8994_setup_interface(struct wm8994_priv *priv,
enum en_audio_interface aif_id)
{
int ret;
/* VMID Selection */
ret = wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_1,
WM8994_VMID_SEL_MASK | WM8994_BIAS_ENA_MASK, 0x3);
/* Charge Pump Enable */
ret |= wm8994_bic_or(priv, WM8994_CHARGE_PUMP_1, WM8994_CP_ENA_MASK,
WM8994_CP_ENA);
/* Head Phone Power Enable */
ret |= wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_1,
WM8994_HPOUT1L_ENA_MASK, WM8994_HPOUT1L_ENA);
ret |= wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_1,
WM8994_HPOUT1R_ENA_MASK, WM8994_HPOUT1R_ENA);
if (aif_id == WM8994_AIF1) {
ret |= wm8994_i2c_write(priv, WM8994_POWER_MANAGEMENT_2,
WM8994_TSHUT_ENA | WM8994_MIXINL_ENA |
WM8994_MIXINR_ENA | WM8994_IN2L_ENA |
WM8994_IN2R_ENA);
ret |= wm8994_i2c_write(priv, WM8994_POWER_MANAGEMENT_4,
WM8994_ADCL_ENA | WM8994_ADCR_ENA |
WM8994_AIF1ADC1R_ENA |
WM8994_AIF1ADC1L_ENA);
/* Power enable for AIF1 and DAC1 */
ret |= wm8994_i2c_write(priv, WM8994_POWER_MANAGEMENT_5,
WM8994_AIF1DACL_ENA |
WM8994_AIF1DACR_ENA |
WM8994_DAC1L_ENA | WM8994_DAC1R_ENA);
} else if (aif_id == WM8994_AIF2) {
/* Power enable for AIF2 and DAC1 */
ret |= wm8994_bic_or(priv, WM8994_POWER_MANAGEMENT_5,
WM8994_AIF2DACL_ENA_MASK | WM8994_AIF2DACR_ENA_MASK |
WM8994_DAC1L_ENA_MASK | WM8994_DAC1R_ENA_MASK,
WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA |
WM8994_DAC1L_ENA | WM8994_DAC1R_ENA);
}
/* Head Phone Initialisation */
ret |= wm8994_bic_or(priv, WM8994_ANALOGUE_HP_1,
WM8994_HPOUT1L_DLY_MASK | WM8994_HPOUT1R_DLY_MASK,
WM8994_HPOUT1L_DLY | WM8994_HPOUT1R_DLY);
ret |= wm8994_bic_or(priv, WM8994_DC_SERVO_1,
WM8994_DCS_ENA_CHAN_0_MASK |
WM8994_DCS_ENA_CHAN_1_MASK , WM8994_DCS_ENA_CHAN_0 |
WM8994_DCS_ENA_CHAN_1);
ret |= wm8994_bic_or(priv, WM8994_ANALOGUE_HP_1,
WM8994_HPOUT1L_DLY_MASK |
WM8994_HPOUT1R_DLY_MASK | WM8994_HPOUT1L_OUTP_MASK |
WM8994_HPOUT1R_OUTP_MASK |
WM8994_HPOUT1L_RMV_SHORT_MASK |
WM8994_HPOUT1R_RMV_SHORT_MASK, WM8994_HPOUT1L_DLY |
WM8994_HPOUT1R_DLY | WM8994_HPOUT1L_OUTP |
WM8994_HPOUT1R_OUTP | WM8994_HPOUT1L_RMV_SHORT |
WM8994_HPOUT1R_RMV_SHORT);
/* MIXER Config DAC1 to HP */
ret |= wm8994_bic_or(priv, WM8994_OUTPUT_MIXER_1,
WM8994_DAC1L_TO_HPOUT1L_MASK,
WM8994_DAC1L_TO_HPOUT1L);
ret |= wm8994_bic_or(priv, WM8994_OUTPUT_MIXER_2,
WM8994_DAC1R_TO_HPOUT1R_MASK,
WM8994_DAC1R_TO_HPOUT1R);
if (aif_id == WM8994_AIF1) {
/* Routing AIF1 to DAC1 */
ret |= wm8994_i2c_write(priv, WM8994_DAC1_LEFT_MIXER_ROUTING,
WM8994_AIF1DAC1L_TO_DAC1L);
ret |= wm8994_i2c_write(priv, WM8994_DAC1_RIGHT_MIXER_ROUTING,
WM8994_AIF1DAC1R_TO_DAC1R);
/* GPIO Settings for AIF1 */
ret |= wm8994_i2c_write(priv, WM8994_GPIO_1,
WM8994_GPIO_DIR_OUTPUT |
WM8994_GPIO_FUNCTION_I2S_CLK |
WM8994_GPIO_INPUT_DEBOUNCE);
ret |= wm8994_init_volume_aif1_dac1(priv);
} else if (aif_id == WM8994_AIF2) {
/* Routing AIF2 to DAC1 */
ret |= wm8994_bic_or(priv, WM8994_DAC1_LEFT_MIXER_ROUTING,
WM8994_AIF2DACL_TO_DAC1L_MASK,
WM8994_AIF2DACL_TO_DAC1L);
ret |= wm8994_bic_or(priv, WM8994_DAC1_RIGHT_MIXER_ROUTING,
WM8994_AIF2DACR_TO_DAC1R_MASK,
WM8994_AIF2DACR_TO_DAC1R);
/* GPIO Settings for AIF2 */
/* B CLK */
ret |= wm8994_bic_or(priv, WM8994_GPIO_3, WM8994_GPIO_DIR_MASK |
WM8994_GPIO_FUNCTION_MASK,
WM8994_GPIO_DIR_OUTPUT);
/* LR CLK */
ret |= wm8994_bic_or(priv, WM8994_GPIO_4, WM8994_GPIO_DIR_MASK |
WM8994_GPIO_FUNCTION_MASK,
WM8994_GPIO_DIR_OUTPUT);
/* DATA */
ret |= wm8994_bic_or(priv, WM8994_GPIO_5, WM8994_GPIO_DIR_MASK |
WM8994_GPIO_FUNCTION_MASK,
WM8994_GPIO_DIR_OUTPUT);
ret |= wm8994_init_volume_aif2_dac1(priv);
}
if (ret < 0)
goto err;
debug("%s: Codec chip setup ok\n", __func__);
return 0;
err:
debug("%s: Codec chip setup error\n", __func__);
return -1;
}
static int _wm8994_init(struct wm8994_priv *priv,
enum en_audio_interface aif_id, int sampling_rate,
int mclk_freq, int bits_per_sample,
unsigned int channels)
{
int ret;
ret = wm8994_setup_interface(priv, aif_id);
if (ret < 0) {
debug("%s: wm8994 codec chip init failed\n", __func__);
return ret;
}
ret = wm8994_set_sysclk(priv, aif_id, WM8994_SYSCLK_MCLK1, mclk_freq);
if (ret < 0) {
debug("%s: wm8994 codec set sys clock failed\n", __func__);
return ret;
}
ret = wm8994_hw_params(priv, aif_id, sampling_rate, bits_per_sample,
channels);
if (ret == 0) {
ret = wm8994_set_fmt(priv, aif_id, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS);
}
return ret;
}
static int wm8994_set_params(struct udevice *dev, int interface, int rate,
int mclk_freq, int bits_per_sample, uint channels)
{
struct wm8994_priv *priv = dev_get_priv(dev);
return _wm8994_init(priv, interface, rate, mclk_freq, bits_per_sample,
channels);
}
static int wm8994_probe(struct udevice *dev)
{
struct wm8994_priv *priv = dev_get_priv(dev);
priv->dev = dev;
return wm8994_device_init(priv);
}
static const struct audio_codec_ops wm8994_ops = {
.set_params = wm8994_set_params,
};
static const struct udevice_id wm8994_ids[] = {
{ .compatible = "wolfson,wm8994" },
{ }
};
U_BOOT_DRIVER(wm8994) = {
.name = "wm8994",
.id = UCLASS_AUDIO_CODEC,
.of_match = wm8994_ids,
.probe = wm8994_probe,
.ops = &wm8994_ops,
.priv_auto_alloc_size = sizeof(struct wm8994_priv),
};
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2012 Samsung Electronics
* R. Chadrasekar <rcsekar@samsung.com>
*/
#ifndef __WM8994_H__
#define __WM8994_H__
/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */
#define WM8994_SYSCLK_MCLK1 1
#define WM8994_SYSCLK_MCLK2 2
#define WM8994_SYSCLK_FLL1 3
#define WM8994_SYSCLK_FLL2 4
/* Avilable audi interface ports in wm8994 codec */
enum en_audio_interface {
WM8994_AIF1,
WM8994_AIF2,
WM8994_AIF3
};
/* OPCLK is also configured with set_dai_sysclk, specify division*10 as rate. */
#define WM8994_SYSCLK_OPCLK 5
#define WM8994_FLL1 1
#define WM8994_FLL2 2
#define WM8994_FLL_SRC_MCLK1 1
#define WM8994_FLL_SRC_MCLK2 2
#define WM8994_FLL_SRC_LRCLK 3
#define WM8994_FLL_SRC_BCLK 4
/* maximum available digital interfac in the dac to configure */
#define WM8994_MAX_AIF 2
#define WM8994_MAX_INPUT_CLK_FREQ 13500000
#define WM8994_ID 0x8994
enum wm8994_vmid_mode {
WM8994_VMID_NORMAL,
WM8994_VMID_FORCE,
};
/* wm 8994 family devices */
enum wm8994_type {
WM8994 = 0,
WM8958 = 1,
WM1811 = 2,
};
/*
* intialise wm8994 sound codec device for the given configuration
*
* @param blob FDT node for codec values
* @param aif_id enum value of codec interface port in which
* soc i2s is connected
* @param sampling_rate Sampling rate ranges between from 8khz to 96khz
* @param mclk_freq Master clock frequency.
* @param bits_per_sample bits per Sample can be 16 or 24
* @param channels Number of channnels, maximum 2
*
* @returns -1 for error and 0 Success.
*/
int wm8994_init(const void *blob, enum en_audio_interface aif_id,
int sampling_rate, int mclk_freq,
int bits_per_sample, unsigned int channels);
#endif /*__WM8994_H__ */
@@ -0,0 +1,325 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* (C) Copyright 2012 Samsung Electronics
*/
#ifndef __WM8994_REGISTERS_H__
#define __WM8994_REGISTERS_H__
/*
* Register values.
*/
#define WM8994_SOFTWARE_RESET 0x00
#define WM8994_POWER_MANAGEMENT_1 0x01
#define WM8994_POWER_MANAGEMENT_2 0x02
#define WM8994_POWER_MANAGEMENT_4 0x04
#define WM8994_POWER_MANAGEMENT_5 0x05
#define WM8994_LEFT_OUTPUT_VOLUME 0x1C
#define WM8994_RIGHT_OUTPUT_VOLUME 0x1D
#define WM8994_OUTPUT_MIXER_1 0x2D
#define WM8994_OUTPUT_MIXER_2 0x2E
#define WM8994_CHARGE_PUMP_1 0x4C
#define WM8994_DC_SERVO_1 0x54
#define WM8994_ANALOGUE_HP_1 0x60
#define WM8994_CHIP_REVISION 0x100
#define WM8994_AIF1_CLOCKING_1 0x200
#define WM8994_AIF1_CLOCKING_2 0x201
#define WM8994_AIF2_CLOCKING_1 0x204
#define WM8994_CLOCKING_1 0x208
#define WM8994_CLOCKING_2 0x209
#define WM8994_AIF1_RATE 0x210
#define WM8994_AIF2_RATE 0x211
#define WM8994_RATE_STATUS 0x212
#define WM8994_AIF1_CONTROL_1 0x300
#define WM8994_AIF1_CONTROL_2 0x301
#define WM8994_AIF1_MASTER_SLAVE 0x302
#define WM8994_AIF1_BCLK 0x303
#define WM8994_AIF2_CONTROL_1 0x310
#define WM8994_AIF2_CONTROL_2 0x311
#define WM8994_AIF2_MASTER_SLAVE 0x312
#define WM8994_AIF2_BCLK 0x313
#define WM8994_AIF1_DAC_FILTERS_1 0x420
#define WM8994_AIF2_DAC_LEFT_VOLUME 0x502
#define WM8994_AIF2_DAC_RIGHT_VOLUME 0x503
#define WM8994_AIF2_DAC_FILTERS_1 0x520
#define WM8994_DAC1_LEFT_MIXER_ROUTING 0x601
#define WM8994_DAC1_RIGHT_MIXER_ROUTING 0x602
#define WM8994_DAC1_LEFT_VOLUME 0x610
#define WM8994_DAC1_RIGHT_VOLUME 0x611
#define WM8994_GPIO_1 0x700
#define WM8994_GPIO_3 0x702
#define WM8994_GPIO_4 0x703
#define WM8994_GPIO_5 0x704
/*
* Field Definitions.
*/
/*
* R0 (0x00) - Software Reset
*/
/* SW_RESET */
#define WM8994_SW_RESET 1
/*
* R1 (0x01) - Power Management (1)
*/
/* HPOUT1L_ENA */
#define WM8994_HPOUT1L_ENA 0x0200
/* HPOUT1L_ENA */
#define WM8994_HPOUT1L_ENA_MASK 0x0200
/* HPOUT1R_ENA */
#define WM8994_HPOUT1R_ENA 0x0100
/* HPOUT1R_ENA */
#define WM8994_HPOUT1R_ENA_MASK 0x0100
/* VMID_SEL - [2:1] */
#define WM8994_VMID_SEL_MASK 0x0006
/* BIAS_ENA */
#define WM8994_BIAS_ENA 0x0001
/* BIAS_ENA */
#define WM8994_BIAS_ENA_MASK 0x0001
/*
* R2 (0x02) - Power Management (2)
*/
/* OPCLK_ENA */
#define WM8994_OPCLK_ENA 0x0800
#define WM8994_TSHUT_ENA 0x4000
#define WM8994_MIXINL_ENA 0x0200
#define WM8994_MIXINR_ENA 0x0100
#define WM8994_IN2L_ENA 0x0080
#define WM8994_IN2R_ENA 0x0020
/*
* R5 (0x04) - Power Management (4)
*/
#define WM8994_ADCL_ENA 0x0001
#define WM8994_ADCR_ENA 0x0002
#define WM8994_AIF1ADC1R_ENA 0x0100
#define WM8994_AIF1ADC1L_ENA 0x0200
/*
* R5 (0x05) - Power Management (5)
*/
/* AIF2DACL_ENA */
#define WM8994_AIF2DACL_ENA 0x2000
#define WM8994_AIF2DACL_ENA_MASK 0x2000
/* AIF2DACR_ENA */
#define WM8994_AIF2DACR_ENA 0x1000
#define WM8994_AIF2DACR_ENA_MASK 0x1000
/* AIF1DACL_ENA */
#define WM8994_AIF1DACL_ENA 0x0200
#define WM8994_AIF1DACL_ENA_MASK 0x0200
/* AIF1DACR_ENA */
#define WM8994_AIF1DACR_ENA 0x0100
#define WM8994_AIF1DACR_ENA_MASK 0x0100
/* DAC1L_ENA */
#define WM8994_DAC1L_ENA 0x0002
#define WM8994_DAC1L_ENA_MASK 0x0002
/* DAC1R_ENA */
#define WM8994_DAC1R_ENA 0x0001
#define WM8994_DAC1R_ENA_MASK 0x0001
/*
* R45 (0x2D) - Output Mixer (1)
*/
/* DAC1L_TO_HPOUT1L */
#define WM8994_DAC1L_TO_HPOUT1L 0x0100
#define WM8994_DAC1L_TO_HPOUT1L_MASK 0x0100
/*
* R46 (0x2E) - Output Mixer (2)
*/
/* DAC1R_TO_HPOUT1R */
#define WM8994_DAC1R_TO_HPOUT1R 0x0100
#define WM8994_DAC1R_TO_HPOUT1R_MASK 0x0100
/*
* R76 (0x4C) - Charge Pump (1)
*/
/* CP_ENA */
#define WM8994_CP_ENA 0x8000
#define WM8994_CP_ENA_MASK 0x8000
/*
* R84 (0x54) - DC Servo (1)
*/
/* DCS_ENA_CHAN_1 */
#define WM8994_DCS_ENA_CHAN_1 0x0002
#define WM8994_DCS_ENA_CHAN_1_MASK 0x0002
/* DCS_ENA_CHAN_0 */
#define WM8994_DCS_ENA_CHAN_0 0x0001
#define WM8994_DCS_ENA_CHAN_0_MASK 0x0001
/*
* R96 (0x60) - Analogue HP (1)
*/
/* HPOUT1L_RMV_SHORT */
#define WM8994_HPOUT1L_RMV_SHORT 0x0080
#define WM8994_HPOUT1L_RMV_SHORT_MASK 0x0080
/* HPOUT1L_OUTP */
#define WM8994_HPOUT1L_OUTP 0x0040
#define WM8994_HPOUT1L_OUTP_MASK 0x0040
/* HPOUT1L_DLY */
#define WM8994_HPOUT1L_DLY 0x0020
#define WM8994_HPOUT1L_DLY_MASK 0x0020
/* HPOUT1R_RMV_SHORT */
#define WM8994_HPOUT1R_RMV_SHORT 0x0008
#define WM8994_HPOUT1R_RMV_SHORT_MASK 0x0008
/* HPOUT1R_OUTP */
#define WM8994_HPOUT1R_OUTP 0x0004
#define WM8994_HPOUT1R_OUTP_MASK 0x0004
/* HPOUT1R_DLY */
#define WM8994_HPOUT1R_DLY 0x0002
#define WM8994_HPOUT1R_DLY_MASK 0x0002
/*
* R512 (0x200) - AIF1 Clocking (1)
*/
/* AIF1CLK_SRC - [4:3] */
#define WM8994_AIF1CLK_SRC_MASK 0x0018
/* AIF1CLK_DIV */
#define WM8994_AIF1CLK_DIV 0x0002
/* AIF1CLK_ENA */
#define WM8994_AIF1CLK_ENA 0x0001
#define WM8994_AIF1CLK_ENA_MASK 0x0001
/*
* R517 (0x205) - AIF2 Clocking (2)
*/
/* AIF2DAC_DIV - [5:3] */
#define WM8994_AIF2DAC_DIV_MASK 0x0038
/*
* R520 (0x208) - Clocking (1)
*/
/* AIF1DSPCLK_ENA */
#define WM8994_AIF1DSPCLK_ENA 0x0008
#define WM8994_AIF1DSPCLK_ENA_MASK 0x0008
/* AIF2DSPCLK_ENA */
#define WM8994_AIF2DSPCLK_ENA 0x0004
#define WM8994_AIF2DSPCLK_ENA_MASK 0x0004
/* SYSDSPCLK_ENA */
#define WM8994_SYSDSPCLK_ENA 0x0002
#define WM8994_SYSDSPCLK_ENA_MASK 0x0002
/* SYSCLK_SRC */
#define WM8994_SYSCLK_SRC 0x0001
/*
* R521 (0x209) - Clocking (2)
*/
/* OPCLK_DIV - [2:0] */
#define WM8994_OPCLK_DIV_MASK 0x0007
/*
* R528 (0x210) - AIF1 Rate
*/
/* AIF1_SR - [7:4] */
#define WM8994_AIF1_SR_MASK 0x00F0
#define WM8994_AIF1_SR_SHIFT 4
/* AIF1CLK_RATE - [3:0] */
#define WM8994_AIF1CLK_RATE_MASK 0x000F
/*
* R768 (0x300) - AIF1 Control (1)
*/
/* AIF1_BCLK_INV */
#define WM8994_AIF1_BCLK_INV 0x0100
/* AIF1_LRCLK_INV */
#define WM8994_AIF1_LRCLK_INV 0x0080
#define WM8994_AIF1_LRCLK_INV_MASK 0x0080
/* AIF1_WL - [6:5] */
#define WM8994_AIF1_WL_MASK 0x0060
/* AIF1_FMT - [4:3] */
#define WM8994_AIF1_FMT_MASK 0x0018
/*
* R769 (0x301) - AIF1 Control (2)
*/
/* AIF1_MONO */
#define WM8994_AIF1_MONO 0x0100
/*
* R770 (0x302) - AIF1 Master/Slave
*/
/* AIF1_MSTR */
#define WM8994_AIF1_MSTR 0x4000
#define WM8994_AIF1_MSTR_MASK 0x4000
/*
* R771 (0x303) - AIF1 BCLK
*/
/* AIF1_BCLK_DIV - [8:4] */
#define WM8994_AIF1_BCLK_DIV_MASK 0x01F0
#define WM8994_AIF1_BCLK_DIV_SHIFT 4
/*
* R1282 (0x502) - AIF2 DAC Left Volume
*/
/* AIF2DAC_VU */
#define WM8994_AIF2DAC_VU 0x0100
#define WM8994_AIF2DAC_VU_MASK 0x0100
/* AIF2DACL_VOL - [7:0] */
#define WM8994_AIF2DACL_VOL_MASK 0x00FF
/*
* R1283 (0x503) - AIF2 DAC Right Volume
*/
/* AIF2DACR_VOL - [7:0] */
#define WM8994_AIF2DACR_VOL_MASK 0x00FF
/*
* R1312 (0x520) - AIF2 DAC Filters (1)
*/
/* AIF2DAC_MUTE */
#define WM8994_AIF2DAC_MUTE_MASK 0x0200
/*
* R1537 (0x601) - DAC1 Left Mixer Routing
*/
/* AIF2DACL_TO_DAC1L */
#define WM8994_AIF2DACL_TO_DAC1L 0x0004
#define WM8994_AIF2DACL_TO_DAC1L_MASK 0x0004
/* AIF1DAC1L_TO_DAC1L */
#define WM8994_AIF1DAC1L_TO_DAC1L 0x0001
/*
* R1538 (0x602) - DAC1 Right Mixer Routing
*/
/* AIF2DACR_TO_DAC1R */
#define WM8994_AIF2DACR_TO_DAC1R 0x0004
#define WM8994_AIF2DACR_TO_DAC1R_MASK 0x0004
/* AIF1DAC1R_TO_DAC1R */
#define WM8994_AIF1DAC1R_TO_DAC1R 0x0001
/*
* R1552 (0x610) - DAC1 Left Volume
*/
/* DAC1L_MUTE */
#define WM8994_DAC1L_MUTE_MASK 0x0200
/* DAC1_VU */
#define WM8994_DAC1_VU 0x0100
#define WM8994_DAC1_VU_MASK 0x0100
/* DAC1L_VOL - [7:0] */
#define WM8994_DAC1L_VOL_MASK 0x00FF
/*
* R1553 (0x611) - DAC1 Right Volume
*/
/* DAC1R_MUTE */
#define WM8994_DAC1R_MUTE_MASK 0x0200
/* DAC1R_VOL - [7:0] */
#define WM8994_DAC1R_VOL_MASK 0x00FF
/*
* GPIO
*/
/* OUTPUT PIN */
#define WM8994_GPIO_DIR_OUTPUT 0x8000
/* GPIO PIN MASK */
#define WM8994_GPIO_DIR_MASK 0xFFE0
/* I2S CLK */
#define WM8994_GPIO_FUNCTION_I2S_CLK 0x0001
#define WM8994_GPIO_INPUT_DEBOUNCE 0x0100
/* GPn FN */
#define WM8994_GPIO_FUNCTION_MASK 0x001F
#endif