diff --git a/drivers/dac/CMakeLists.txt b/drivers/dac/CMakeLists.txt index 630c3058c9e..ed605f3bfe2 100644 --- a/drivers/dac/CMakeLists.txt +++ b/drivers/dac/CMakeLists.txt @@ -5,5 +5,6 @@ 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_SAM0 dac_sam0.c) zephyr_library_sources_ifdef(CONFIG_DAC_SHELL dac_shell.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE dac_handlers.c) diff --git a/drivers/dac/Kconfig b/drivers/dac/Kconfig index d446a9b3048..c3dfeb54eb8 100644 --- a/drivers/dac/Kconfig +++ b/drivers/dac/Kconfig @@ -28,4 +28,6 @@ source "drivers/dac/Kconfig.mcux" source "drivers/dac/Kconfig.stm32" +source "drivers/dac/Kconfig.sam0" + endif # DAC diff --git a/drivers/dac/Kconfig.sam0 b/drivers/dac/Kconfig.sam0 new file mode 100644 index 00000000000..622a435eab6 --- /dev/null +++ b/drivers/dac/Kconfig.sam0 @@ -0,0 +1,9 @@ +# Copyright (c) 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config DAC_SAM0 + bool "Atmel SAM0 series DAC Driver" + default n + depends on SOC_SERIES_SAMD20 || SOC_SERIES_SAMD21 + help + Enables the Atmel SAM0 MCU Family Digital-to-Analog (DAC) driver. diff --git a/drivers/dac/dac_sam0.c b/drivers/dac/dac_sam0.c new file mode 100644 index 00000000000..62393bff0a3 --- /dev/null +++ b/drivers/dac/dac_sam0.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_sam0_dac + +#include + +#include +#include + +/* + * Maps between the DTS reference property names and register values. Note that + * the ASF uses the 09/2015 names which differ from the 03/2020 datasheet. + * + * TODO(#21273): replace once improved support for enum values lands. + */ +#define SAM0_DAC_REFSEL_0 DAC_CTRLB_REFSEL_INT1V_Val +#define SAM0_DAC_REFSEL_1 DAC_CTRLB_REFSEL_AVCC_Val +#define SAM0_DAC_REFSEL_2 DAC_CTRLB_REFSEL_VREFP_Val + +struct dac_sam0_cfg { + Dac *regs; + uint8_t pm_apbc_bit; + uint8_t gclk_clkctrl_id; + uint8_t refsel; +}; + +#define DEV_CFG(dev) ((const struct dac_sam0_cfg *const)(dev)->config_info) + +/* Write to the DAC. */ +static int dac_sam0_write_value(struct device *dev, uint8_t channel, + uint32_t value) +{ + const struct dac_sam0_cfg *const cfg = DEV_CFG(dev); + Dac *regs = cfg->regs; + + regs->DATA.reg = (uint16_t)value; + + return 0; +} + +/* + * Setup the channel. As the SAM0 has one fixed width channel, this validates + * the input and does nothing else. + */ +static int dac_sam0_channel_setup(struct device *dev, + const struct dac_channel_cfg *channel_cfg) +{ + if (channel_cfg->channel_id != 0) { + return -EINVAL; + } + if (channel_cfg->resolution != 10) { + return -ENOTSUP; + } + + return 0; +} + +/* Initialise and enable the DAC. */ +static int dac_sam0_init(struct device *dev) +{ + const struct dac_sam0_cfg *const cfg = DEV_CFG(dev); + Dac *regs = cfg->regs; + + /* Enable the GCLK */ + GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_CLKEN; + + /* Enable the clock in PM */ + PM->APBCMASK.reg |= 1 << cfg->pm_apbc_bit; + + /* Reset then configure the DAC */ + regs->CTRLA.bit.SWRST = 1; + while (regs->STATUS.bit.SYNCBUSY) { + } + + regs->CTRLB.bit.REFSEL = cfg->refsel; + regs->CTRLB.bit.EOEN = 1; + + /* Enable */ + regs->CTRLA.bit.ENABLE = 1; + while (regs->STATUS.bit.SYNCBUSY) { + } + + return 0; +} + +static const struct dac_driver_api api_sam0_driver_api = { + .channel_setup = dac_sam0_channel_setup, + .write_value = dac_sam0_write_value +}; + +#define SAM0_DAC_REFSEL(n) \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, reference), \ + (DT_ENUM_IDX(DT_DRV_INST(n), reference)), (0)) + +#define SAM0_DAC_INIT(n) \ + static const struct dac_sam0_cfg dac_sam0_cfg_##n = { \ + .regs = (Dac *)DT_INST_REG_ADDR(n), \ + .pm_apbc_bit = DT_INST_CLOCKS_CELL_BY_NAME(n, pm, bit), \ + .gclk_clkctrl_id = \ + DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, clkctrl_id), \ + .refsel = UTIL_CAT(SAM0_DAC_REFSEL_, SAM0_DAC_REFSEL(n)), \ + }; \ + \ + DEVICE_AND_API_INIT(dac_##n, DT_INST_LABEL(n), &dac_sam0_init, NULL, \ + &dac_sam0_cfg_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &api_sam0_driver_api) + +DT_INST_FOREACH_STATUS_OKAY(SAM0_DAC_INIT); diff --git a/soc/arm/atmel_sam0/common/Kconfig.defconfig.series b/soc/arm/atmel_sam0/common/Kconfig.defconfig.series index c7ea4ecb9ed..8c360df46c6 100644 --- a/soc/arm/atmel_sam0/common/Kconfig.defconfig.series +++ b/soc/arm/atmel_sam0/common/Kconfig.defconfig.series @@ -11,6 +11,9 @@ config ADC_SAM0 config COUNTER_SAM0_TC32 default COUNTER +config DAC_SAM0 + default DAC + config DMA_SAM0 default DMA