diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 0d6051cf980..e5355166440 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -5,6 +5,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC dac_mcux_dac.c) zephyr_library_sources_ifdef(CONFIG_DAC_MCUX_DAC32 dac_mcux_dac32.c) zephyr_library_sources_ifdef(CONFIG_DAC_STM32 dac_stm32.c) +zephyr_library_sources_ifdef(CONFIG_DAC_SAM dac_sam.c) zephyr_library_sources_ifdef(CONFIG_DAC_SAM0 dac_sam0.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX0508 dac_dacx0508.c) zephyr_library_sources_ifdef(CONFIG_DAC_DACX3608 dac_dacx3608.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index e50e640f9cc..ce9ce9879ef 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -28,6 +28,8 @@ source "drivers/dac/Kconfig.mcux" source "drivers/dac/Kconfig.stm32" +source "drivers/dac/Kconfig.sam" + source "drivers/dac/Kconfig.sam0" source "drivers/dac/Kconfig.dacx0508" diff --git a/drivers/dac/Kconfig.sam b/drivers/dac/Kconfig.sam new file mode 100644 index 00000000000..562b8760dc5 --- /dev/null +++ b/drivers/dac/Kconfig.sam @@ -0,0 +1,13 @@ +# Copyright (c) 2021 Piotr Mienkowski +# SPDX-License-Identifier: Apache-2.0 +# + +# Workaround for not being able to have commas in macro arguments +DT_COMPAT_ATMEL_SAM_DAC := atmel,sam-dac + +config DAC_SAM + bool "Atmel SAM DAC driver" + default $(dt_compat_enabled,$(DT_COMPAT_ATMEL_SAM_DAC)) + depends on SOC_FAMILY_SAM + help + Enable Atmel SAM MCU Family Digital Audio Converter (DAC) driver. diff --git a/drivers/dac/dac_sam.c b/drivers/dac/dac_sam.c new file mode 100644 index 00000000000..18ec30fde25 --- /dev/null +++ b/drivers/dac/dac_sam.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 Piotr Mienkowski + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief DAC driver for Atmel SAM MCU family. + * + * Remarks: + * Only SAME70, SAMV71 series devices are currently supported. Please submit a + * patch. + */ + +#define DT_DRV_COMPAT atmel_sam_dac + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(dac_sam, CONFIG_DAC_LOG_LEVEL); + +BUILD_ASSERT(IS_ENABLED(CONFIG_SOC_SERIES_SAME70) || + IS_ENABLED(CONFIG_SOC_SERIES_SAMV71), + "Only SAME70, SAMV71 series devices are currently supported."); + +#define DAC_CHANNEL_NO 2 + +/* Device constant configuration parameters */ +struct dac_sam_dev_cfg { + Dacc *regs; + void (*irq_config)(void); + uint8_t irq_id; + uint8_t periph_id; + uint8_t prescaler; +}; + +struct dac_channel { + struct k_sem sem; +}; + +/* Device run time data */ +struct dac_sam_dev_data { + struct dac_channel dac_channels[DAC_CHANNEL_NO]; +}; + +#define DEV_NAME(dev) ((dev)->name) +#define DEV_CFG(dev) \ + ((const struct dac_sam_dev_cfg *const)(dev)->config) +#define DEV_DATA(dev) \ + ((struct dac_sam_dev_data *const)(dev)->data) + +static void dac_sam_isr(void *arg) +{ + const struct device *dev = (const struct device *)arg; + const struct dac_sam_dev_cfg *const dev_cfg = DEV_CFG(dev); + struct dac_sam_dev_data *const dev_data = DEV_DATA(dev); + Dacc *const dac = dev_cfg->regs; + uint32_t int_stat; + + /* Retrieve interrupt status */ + int_stat = dac->DACC_ISR & dac->DACC_IMR; + + if ((int_stat & DACC_ISR_TXRDY0) != 0) { + /* Disable Transmit Ready Interrupt */ + dac->DACC_IDR = DACC_IDR_TXRDY0; + k_sem_give(&dev_data->dac_channels[0].sem); + } + if ((int_stat & DACC_ISR_TXRDY1) != 0) { + /* Disable Transmit Ready Interrupt */ + dac->DACC_IDR = DACC_IDR_TXRDY1; + k_sem_give(&dev_data->dac_channels[1].sem); + } +} + +static int dac_sam_channel_setup(const struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + const struct dac_sam_dev_cfg *const dev_cfg = DEV_CFG(dev); + Dacc *const dac = dev_cfg->regs; + + if (channel_cfg->channel_id >= DAC_CHANNEL_NO) { + return -EINVAL; + } + if (channel_cfg->resolution != 12) { + return -ENOTSUP; + } + + /* Enable Channel */ + dac->DACC_CHER = DACC_CHER_CH0 << channel_cfg->channel_id; + + return 0; +} + +static int dac_sam_write_value(const struct device *dev, uint8_t channel, + uint32_t value) +{ + struct dac_sam_dev_data *const dev_data = DEV_DATA(dev); + const struct dac_sam_dev_cfg *const dev_cfg = DEV_CFG(dev); + Dacc *const dac = dev_cfg->regs; + + if (channel >= DAC_CHANNEL_NO) { + return -EINVAL; + } + + if (dac->DACC_IMR & (DACC_IMR_TXRDY0 << channel)) { + /* Attempting to send data on channel that's already in use */ + return -EINVAL; + } + + k_sem_take(&dev_data->dac_channels[channel].sem, K_FOREVER); + + /* Trigger conversion */ + dac->DACC_CDR[channel] = DACC_CDR_DATA0(value); + + /* Enable Transmit Ready Interrupt */ + dac->DACC_IER = DACC_IER_TXRDY0 << channel; + + return 0; +} + +static int dac_sam_init(const struct device *dev) +{ + const struct dac_sam_dev_cfg *const dev_cfg = DEV_CFG(dev); + struct dac_sam_dev_data *const dev_data = DEV_DATA(dev); + Dacc *const dac = dev_cfg->regs; + + /* Configure interrupts */ + dev_cfg->irq_config(); + + /* Initialize semaphores */ + for (int i = 0; i < ARRAY_SIZE(dev_data->dac_channels); i++) { + k_sem_init(&dev_data->dac_channels[i].sem, 1, 1); + } + + /* Enable DAC clock in PMC */ + soc_pmc_peripheral_enable(dev_cfg->periph_id); + + /* Set Mode Register */ + dac->DACC_MR = DACC_MR_PRESCALER(dev_cfg->prescaler); + + /* Enable module's IRQ */ + irq_enable(dev_cfg->irq_id); + + LOG_INF("Device %s initialized", DEV_NAME(dev)); + + return 0; +} + +static const struct dac_driver_api dac_sam_driver_api = { + .channel_setup = dac_sam_channel_setup, + .write_value = dac_sam_write_value, +}; + +/* DACC */ + +static void dacc_irq_config(void) +{ + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), dac_sam_isr, + DEVICE_DT_INST_GET(0), 0); +} + +static const struct dac_sam_dev_cfg dacc_sam_config = { + .regs = (Dacc *)DT_INST_REG_ADDR(0), + .irq_id = DT_INST_IRQN(0), + .irq_config = dacc_irq_config, + .periph_id = DT_INST_PROP(0, peripheral_id), + .prescaler = DT_INST_PROP(0, prescaler), +}; + +static struct dac_sam_dev_data dacc_sam_data; + +DEVICE_DT_INST_DEFINE(0, dac_sam_init, NULL, &dacc_sam_data, &dacc_sam_config, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &dac_sam_driver_api); diff --git a/dts/arm/atmel/same70.dtsi b/dts/arm/atmel/same70.dtsi index 63666e2b86e..9173c3212a6 100644 --- a/dts/arm/atmel/same70.dtsi +++ b/dts/arm/atmel/same70.dtsi @@ -238,6 +238,16 @@ pinctrl-0 = <&pd9c_afec1_adtrg>; }; + dacc: dacc@40040000 { + compatible = "atmel,sam-dac"; + reg = <0x40040000 0x100>; + interrupts = <30 0>; + peripheral-id = <30>; + status = "disabled"; + label = "DACC"; + #io-channel-cells = <1>; + }; + pinctrl@400e0e00 { compatible = "atmel,sam-pinctrl"; #address-cells = <1>; diff --git a/dts/bindings/dac/atmel,sam-dac.yaml b/dts/bindings/dac/atmel,sam-dac.yaml new file mode 100644 index 00000000000..0de27718080 --- /dev/null +++ b/dts/bindings/dac/atmel,sam-dac.yaml @@ -0,0 +1,34 @@ +# Copyright (c) 2021 Piotr Mienkowski +# SPDX-License-Identifier: Apache-2.0 + +description: Atmel SAM family DAC + +compatible: "atmel,sam-dac" + +include: dac-controller.yaml + +properties: + reg: + required: true + + peripheral-id: + type: int + required: true + description: | + peripheral ID + + prescaler: + type: int + required: false + default: 15 + description: | + Peripheral Clock to DAC Clock Ratio. Prescaler value is calcuated as + PRESCAL = (MCK / DACClock) - 2. Should be in range from 0 to 15. The + value will be written to DACC_MR.PRESCALER bit-field. The property is + applicable only to SAME70, SAMV71 series devices. + + "#io-channel-cells": + const: 1 + +io-channel-cells: + - output