|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * |
| 4 | + * Copyright (c) 2025 Silicon Signals Pvt. Ltd. |
| 5 | + * Author: Rutvij Trivedi <rutvij.trivedi@siliconsignals.io> |
| 6 | + * Author: Tarang Raval <tarang.raval@siliconsignals.io> |
| 7 | + */ |
| 8 | + |
| 9 | +#include <zephyr/drivers/i2c.h> |
| 10 | +#include <zephyr/audio/codec.h> |
| 11 | +#include <zephyr/device.h> |
| 12 | +#include <zephyr/logging/log.h> |
| 13 | +#include "max98091.h" |
| 14 | + |
| 15 | +LOG_MODULE_REGISTER(maxim_max98091); |
| 16 | + |
| 17 | +#define DT_DRV_COMPAT maxim_max98091 |
| 18 | + |
| 19 | +struct max98091_config { |
| 20 | + struct i2c_dt_spec i2c; |
| 21 | + uint32_t mclk_freq; |
| 22 | +}; |
| 23 | + |
| 24 | +static void max98091_write_reg(const struct device *dev, uint8_t reg, uint8_t val) |
| 25 | +{ |
| 26 | + const struct max98091_config *const dev_cfg = dev->config; |
| 27 | + |
| 28 | + i2c_reg_write_byte_dt(&dev_cfg->i2c, reg, val); |
| 29 | +} |
| 30 | + |
| 31 | +static void max98091_read_reg(const struct device *dev, uint8_t reg, uint8_t *val) |
| 32 | +{ |
| 33 | + const struct max98091_config *const dev_cfg = dev->config; |
| 34 | + |
| 35 | + i2c_reg_read_byte_dt(&dev_cfg->i2c, reg, val); |
| 36 | +} |
| 37 | + |
| 38 | +static void max98091_update_reg(const struct device *dev, uint8_t reg, uint8_t mask, uint8_t val) |
| 39 | +{ |
| 40 | + const struct max98091_config *const dev_cfg = dev->config; |
| 41 | + |
| 42 | + i2c_reg_update_byte_dt(&dev_cfg->i2c, reg, mask, val); |
| 43 | +} |
| 44 | + |
| 45 | +static void max98091_soft_reset(const struct device *dev) |
| 46 | +{ |
| 47 | + max98091_write_reg(dev, M98091_REG_SOFTWARE_RESET, 0x01); |
| 48 | + k_msleep(20); |
| 49 | +} |
| 50 | + |
| 51 | +/* Configuration Functions */ |
| 52 | +static int max98091_protocol_config(const struct device *dev, audio_dai_type_t dai_type) |
| 53 | +{ |
| 54 | + uint8_t fmt_reg = 0; |
| 55 | + |
| 56 | + switch (dai_type) { |
| 57 | + case AUDIO_DAI_TYPE_I2S: |
| 58 | + fmt_reg |= M98091_I2S_S_MASK; |
| 59 | + break; |
| 60 | + case AUDIO_DAI_TYPE_LEFT_JUSTIFIED: |
| 61 | + fmt_reg |= M98091_LJ_S_MASK; |
| 62 | + break; |
| 63 | + case AUDIO_DAI_TYPE_RIGHT_JUSTIFIED: |
| 64 | + fmt_reg |= M98091_RJ_S_MASK; |
| 65 | + break; |
| 66 | + default: |
| 67 | + LOG_ERR("Unsupported DAI type: %d", dai_type); |
| 68 | + return -EINVAL; |
| 69 | + } |
| 70 | + max98091_write_reg(dev, M98091_REG_DAI_INTERFACE, fmt_reg); |
| 71 | + LOG_DBG("Protocol configured: 0x%02x", fmt_reg); |
| 72 | + return 0; |
| 73 | +} |
| 74 | + |
| 75 | +static int max98091_audio_fmt_config(const struct device *dev, audio_dai_cfg_t *cfg) |
| 76 | +{ |
| 77 | + uint8_t sample_rate; |
| 78 | + uint8_t channels; |
| 79 | + uint8_t word_size; |
| 80 | + |
| 81 | + switch (cfg->i2s.frame_clk_freq) { |
| 82 | + case 8000: |
| 83 | + sample_rate = M98091_SR_8K_MASK; |
| 84 | + break; |
| 85 | + case 16000: |
| 86 | + sample_rate = M98091_SR_16K_MASK; |
| 87 | + break; |
| 88 | + case 32000: |
| 89 | + sample_rate = M98091_SR_32K_MASK; |
| 90 | + break; |
| 91 | + case 44100: |
| 92 | + sample_rate = M98091_SR_44K1_MASK; |
| 93 | + break; |
| 94 | + case 48000: |
| 95 | + sample_rate = M98091_SR_48K_MASK; |
| 96 | + break; |
| 97 | + case 96000: |
| 98 | + sample_rate = M98091_SR_96K_MASK; |
| 99 | + break; |
| 100 | + default: |
| 101 | + LOG_ERR("Unsupported sample rate: %d", cfg->i2s.frame_clk_freq); |
| 102 | + return -EINVAL; |
| 103 | + } |
| 104 | + |
| 105 | + max98091_write_reg(dev, M98091_REG_QUICK_SAMPLE_RATE, sample_rate); |
| 106 | + |
| 107 | + switch (cfg->i2s.channels) { |
| 108 | + case 1: /* Mono */ |
| 109 | + channels = 1; |
| 110 | + break; |
| 111 | + case 2: /* Stereo */ |
| 112 | + channels = 0; |
| 113 | + break; |
| 114 | + default: |
| 115 | + LOG_ERR("Unsupported channels: %d", cfg->i2s.channels); |
| 116 | + return -EINVAL; |
| 117 | + } |
| 118 | + max98091_update_reg(dev, M98091_REG_IO_CONFIGURATION, M98091_DMONO_MASK, channels); |
| 119 | + |
| 120 | + switch (cfg->i2s.word_size) { |
| 121 | + case 16: |
| 122 | + word_size = M98091_16B_WS; |
| 123 | + break; |
| 124 | + default: |
| 125 | + LOG_ERR("Word size %d bits not supported; falling back to 16 bits", |
| 126 | + cfg->i2s.word_size); |
| 127 | + word_size = M98091_16B_WS; |
| 128 | + break; |
| 129 | + } |
| 130 | + max98091_update_reg(dev, M98091_REG_INTERFACE_FORMAT, M98091_WS_MASK, word_size); |
| 131 | + |
| 132 | + return 0; |
| 133 | +} |
| 134 | + |
| 135 | +static void max98091_set_system_clock(const struct device *dev, uint32_t mclk_freq) |
| 136 | +{ |
| 137 | + uint8_t psclk; |
| 138 | + |
| 139 | + if (mclk_freq >= 10000000 && mclk_freq <= 20000000) { |
| 140 | + psclk = M98091_PSCLK_DIV1; |
| 141 | + } else if (mclk_freq > 20000000 && mclk_freq <= 40000000) { |
| 142 | + psclk = M98091_PSCLK_DIV2; |
| 143 | + } else if (mclk_freq > 40000000 && mclk_freq <= 60000000) { |
| 144 | + psclk = M98091_PSCLK_DIV4; |
| 145 | + } else { |
| 146 | + LOG_ERR("Invalid MCLK frequency: %u", mclk_freq); |
| 147 | + return; |
| 148 | + } |
| 149 | + max98091_write_reg(dev, M98091_REG_SYSTEM_CLOCK, psclk); |
| 150 | + LOG_DBG("System clock set: PSCLK=0x%02x", psclk); |
| 151 | + |
| 152 | + max98091_update_reg(dev, M98091_REG_MASTER_MODE, M98091_MAS_MASK, 0); |
| 153 | +} |
| 154 | + |
| 155 | +static int max98091_set_volume_or_mute(const struct device *dev, audio_channel_t channel, int value, |
| 156 | + bool is_volume) |
| 157 | +{ |
| 158 | + uint8_t hp_mask = is_volume ? M98091_HPVOLL_MASK : M98091_HPLM_MASK; |
| 159 | + uint8_t spk_mask = is_volume ? M98091_SPVOLL_MASK : M98091_SPLM_MASK; |
| 160 | + |
| 161 | + switch (channel) { |
| 162 | + case AUDIO_CHANNEL_FRONT_LEFT: |
| 163 | + max98091_update_reg(dev, M98091_REG_LEFT_SPK_VOLUME, spk_mask, value); |
| 164 | + return 0; |
| 165 | + |
| 166 | + case AUDIO_CHANNEL_FRONT_RIGHT: |
| 167 | + max98091_update_reg(dev, M98091_REG_RIGHT_SPK_VOLUME, spk_mask, value); |
| 168 | + return 0; |
| 169 | + |
| 170 | + case AUDIO_CHANNEL_HEADPHONE_LEFT: |
| 171 | + max98091_update_reg(dev, M98091_REG_LEFT_HP_VOLUME, hp_mask, value); |
| 172 | + return 0; |
| 173 | + |
| 174 | + case AUDIO_CHANNEL_HEADPHONE_RIGHT: |
| 175 | + max98091_update_reg(dev, M98091_REG_RIGHT_HP_VOLUME, hp_mask, value); |
| 176 | + return 0; |
| 177 | + |
| 178 | + case AUDIO_CHANNEL_ALL: |
| 179 | + max98091_update_reg(dev, M98091_REG_LEFT_SPK_VOLUME, spk_mask, value); |
| 180 | + max98091_update_reg(dev, M98091_REG_RIGHT_SPK_VOLUME, spk_mask, value); |
| 181 | + max98091_update_reg(dev, M98091_REG_LEFT_HP_VOLUME, hp_mask, value); |
| 182 | + max98091_update_reg(dev, M98091_REG_RIGHT_HP_VOLUME, hp_mask, value); |
| 183 | + return 0; |
| 184 | + |
| 185 | + default: |
| 186 | + return -EINVAL; |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +static int max98091_out_volume_config(const struct device *dev, audio_channel_t channel, int volume) |
| 191 | +{ |
| 192 | + return max98091_set_volume_or_mute(dev, channel, volume, true); |
| 193 | +} |
| 194 | + |
| 195 | +static int max98091_out_mute_config(const struct device *dev, audio_channel_t channel, bool mute) |
| 196 | +{ |
| 197 | + return max98091_set_volume_or_mute(dev, channel, mute, false); |
| 198 | +} |
| 199 | + |
| 200 | +/* Audio Path Configuration */ |
| 201 | +static void max98091_configure_output(const struct device *dev) |
| 202 | +{ |
| 203 | + max98091_update_reg(dev, M98091_REG_IO_CONFIGURATION, M98091_SDIEN_MASK, M98091_SDIEN_MASK); |
| 204 | + |
| 205 | + max98091_write_reg(dev, M98091_REG_LEFT_SPK_MIXER, M98091_MIXSPL_DACL_MASK); |
| 206 | + max98091_write_reg(dev, M98091_REG_RIGHT_SPK_MIXER, M98091_MIXSPR_DACR_MASK); |
| 207 | + |
| 208 | + /* select DAC only source */ |
| 209 | + max98091_write_reg(dev, M98091_REG_HP_CONTROL, 0x00); |
| 210 | + |
| 211 | + /* M98091_HPREN_MASK, M98091_HPLEN_MASK, M98091_SPREN_MASK, |
| 212 | + * M98091_SPLEN_MASK, M98091_DAREN_MASK, M98091_DALEN_MASK |
| 213 | + */ |
| 214 | + max98091_write_reg(dev, M98091_REG_OUTPUT_ENABLE, 0xf3); |
| 215 | + |
| 216 | + max98091_out_volume_config(dev, AUDIO_CHANNEL_ALL, M98091_DEFAULT_VOLUME); |
| 217 | + max98091_out_mute_config(dev, AUDIO_CHANNEL_ALL, false); |
| 218 | +} |
| 219 | + |
| 220 | +static void max98091_start_output(const struct device *dev) |
| 221 | +{ |
| 222 | + ARG_UNUSED(dev); |
| 223 | +} |
| 224 | + |
| 225 | +static void max98091_stop_output(const struct device *dev) |
| 226 | +{ |
| 227 | + ARG_UNUSED(dev); |
| 228 | +} |
| 229 | + |
| 230 | +static int max98091_set_property(const struct device *dev, audio_property_t property, |
| 231 | + audio_channel_t channel, audio_property_value_t val) |
| 232 | +{ |
| 233 | + switch (property) { |
| 234 | + case AUDIO_PROPERTY_OUTPUT_VOLUME: |
| 235 | + max98091_out_volume_config(dev, channel, val.vol); |
| 236 | + case AUDIO_PROPERTY_OUTPUT_MUTE: |
| 237 | + max98091_out_mute_config(dev, channel, val.mute); |
| 238 | + default: |
| 239 | + return -EINVAL; |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +static int max98091_configure(const struct device *dev, struct audio_codec_cfg *cfg) |
| 244 | +{ |
| 245 | + const struct max98091_config *const dev_cfg = dev->config; |
| 246 | + |
| 247 | + if (cfg->dai_type >= AUDIO_DAI_TYPE_INVALID) { |
| 248 | + LOG_ERR("dai_type not supported"); |
| 249 | + return -EINVAL; |
| 250 | + } |
| 251 | + |
| 252 | + max98091_soft_reset(dev); |
| 253 | + |
| 254 | + if (cfg->dai_route == AUDIO_ROUTE_BYPASS) { |
| 255 | + return 0; |
| 256 | + } |
| 257 | + |
| 258 | + /* Put the audio codec into shutdown mode */ |
| 259 | + max98091_write_reg(dev, M98091_REG_DEVICE_SHUTDOWN, 0x00); |
| 260 | + |
| 261 | + max98091_write_reg(dev, M98091_REG_DAC_CONTROL, 0x00); |
| 262 | + |
| 263 | + max98091_write_reg(dev, M98091_REG_TDM_CONTROL, 0x00); |
| 264 | + |
| 265 | + /* Set DLY = 1 to conform to the I2S standard. |
| 266 | + * DLY is only effective when TDM = 0 |
| 267 | + */ |
| 268 | + max98091_write_reg(dev, M98091_REG_INTERFACE_FORMAT, M98091_DLY_MASK); |
| 269 | + |
| 270 | + /* Configure system clock */ |
| 271 | + max98091_set_system_clock(dev, dev_cfg->mclk_freq); |
| 272 | + |
| 273 | + max98091_protocol_config(dev, cfg->dai_type); |
| 274 | + max98091_audio_fmt_config(dev, &cfg->dai_cfg); |
| 275 | + |
| 276 | + /* Configure audio paths based on route */ |
| 277 | + switch (cfg->dai_route) { |
| 278 | + case AUDIO_ROUTE_PLAYBACK: |
| 279 | + max98091_configure_output(dev); |
| 280 | + break; |
| 281 | + default: |
| 282 | + LOG_DBG("Unsupported audio route selected"); |
| 283 | + break; |
| 284 | + } |
| 285 | + |
| 286 | + /* Bring the audio codec out of shutdown mode */ |
| 287 | + max98091_write_reg(dev, M98091_REG_DEVICE_SHUTDOWN, M98091_SHDNN_MASK); |
| 288 | + |
| 289 | + return 0; |
| 290 | +} |
| 291 | + |
| 292 | +static const struct audio_codec_api max98091_api = { |
| 293 | + .configure = max98091_configure, |
| 294 | + .start_output = max98091_start_output, |
| 295 | + .stop_output = max98091_stop_output, |
| 296 | + .set_property = max98091_set_property, |
| 297 | +}; |
| 298 | + |
| 299 | +static int max98091_init(const struct device *dev) |
| 300 | +{ |
| 301 | + const struct max98091_config *cfg_tan = dev->config; |
| 302 | + uint8_t device_id; |
| 303 | + |
| 304 | + if (!i2c_is_ready_dt(&cfg_tan->i2c)) { |
| 305 | + LOG_ERR("I2C bus not ready"); |
| 306 | + return -ENODEV; |
| 307 | + } |
| 308 | + |
| 309 | + max98091_read_reg(dev, M98091_REG_REVISION_ID, &device_id); |
| 310 | + if (device_id >= M98091_REVA && (device_id <= M98091_REVA + 0x0f)) { |
| 311 | + LOG_INF("MAX98091 Device ID: 0x%02X", device_id); |
| 312 | + return 0; |
| 313 | + } |
| 314 | + |
| 315 | + LOG_ERR("Invalid MAX98091 Device ID: 0x%02X", device_id); |
| 316 | + return -EINVAL; |
| 317 | +} |
| 318 | + |
| 319 | +#define MAX98091_INIT(inst) \ |
| 320 | + static const struct max98091_config max98091_config_##inst = { \ |
| 321 | + .i2c = I2C_DT_SPEC_INST_GET(inst), \ |
| 322 | + .mclk_freq = DT_INST_PROP(inst, mclk_frequency)}; \ |
| 323 | + DEVICE_DT_INST_DEFINE(inst, max98091_init, NULL, NULL, &max98091_config_##inst, \ |
| 324 | + POST_KERNEL, CONFIG_AUDIO_CODEC_INIT_PRIORITY, &max98091_api); |
| 325 | + |
| 326 | +DT_INST_FOREACH_STATUS_OKAY(MAX98091_INIT) |
0 commit comments