| /* |
| * Copyright (c) 2020 Google LLC. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT atmel_sam0_dac |
| |
| #include <errno.h> |
| |
| #include <zephyr/drivers/dac.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <soc.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(dac_sam0, CONFIG_DAC_LOG_LEVEL); |
| |
| /* |
| * 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; |
| const struct pinctrl_dev_config *pcfg; |
| uint8_t pm_apbc_bit; |
| uint8_t gclk_clkctrl_id; |
| uint8_t refsel; |
| }; |
| |
| /* Write to the DAC. */ |
| static int dac_sam0_write_value(const struct device *dev, uint8_t channel, |
| uint32_t value) |
| { |
| const struct dac_sam0_cfg *const cfg = dev->config; |
| Dac *regs = cfg->regs; |
| |
| if (value >= BIT(12)) { |
| LOG_ERR("value %d out of range", value); |
| return -EINVAL; |
| } |
| |
| 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(const 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; |
| } |
| |
| if (channel_cfg->internal) { |
| return -ENOSYS; |
| } |
| |
| return 0; |
| } |
| |
| /* Initialise and enable the DAC. */ |
| static int dac_sam0_init(const struct device *dev) |
| { |
| const struct dac_sam0_cfg *const cfg = dev->config; |
| Dac *regs = cfg->regs; |
| int retval; |
| |
| /* Enable the GCLK */ |
| GCLK->CLKCTRL.reg = cfg->gclk_clkctrl_id | GCLK_CLKCTRL_GEN_GCLK0 | |
| GCLK_CLKCTRL_CLKEN; |
| |
| retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (retval < 0) { |
| return retval; |
| } |
| |
| /* 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_INST_ENUM_IDX(n, reference)), (0)) |
| |
| #define SAM0_DAC_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| static const struct dac_sam0_cfg dac_sam0_cfg_##n = { \ |
| .regs = (Dac *)DT_INST_REG_ADDR(n), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(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_DT_INST_DEFINE(n, &dac_sam0_init, NULL, NULL, \ |
| &dac_sam0_cfg_##n, POST_KERNEL, \ |
| CONFIG_DAC_INIT_PRIORITY, \ |
| &api_sam0_driver_api) |
| |
| DT_INST_FOREACH_STATUS_OKAY(SAM0_DAC_INIT); |