| /* |
| * Copyright (c) 2021 ITE Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ite_it8xxx2_tach |
| |
| /** |
| * @file |
| * @brief ITE it8xxx2 tachometer sensor module driver |
| * |
| * This file contains a driver for the tachometer sensor module which contains |
| * two independent counters (F1TL/MRR and F2TL/MRR). The content of the |
| * Tachometer Reading Register is still update based on the sampling counter |
| * that samples the tachometer input (T0A, T0B, T1A or T1B pins). |
| * The following is block diagram of this module: |
| * |
| * Sample Rate = TACH_FREQ / 128 |
| * | |
| * | Tachometer 0 | T0A (GPD6) |
| * | | | +-----------+ | |
| * | +-----+-----+ | | _ _ |<--+ |
| * |------>| F1TL/MRR |<-+-| | |_| |_ |<--+ |
| * | +-----------+ +-----------+ | |
| * | capture pulses T0B (GPJ2) |
| * | in sample rate |
| * | period |
| * +-----------+ | |
| * Crystal-->| Prescaler |--->| Tachometer 1 T1A (GPD7) |
| * 32.768k +-----------+ | | +-----------+ | |
| * | +-----+-----+ | _ _ |<--+ |
| * |------>| F2TL/MRR |<-+-| | |_| |_ |<--+ |
| * | +-----------+ +-----------+ | |
| * | capture pulses T1B (GPJ3) |
| * | in one second |
| * | period |
| * | |
| * |
| * Based on the counter value, we can compute the current RPM of external signal |
| * from encoders. |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/dt-bindings/sensor/it8xxx2_tach.h> |
| #include <errno.h> |
| #include <soc.h> |
| #include <soc_dt.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(tach_ite_it8xxx2, CONFIG_SENSOR_LOG_LEVEL); |
| |
| /* |
| * NOTE: The PWM output maximum is 324Hz in EC LPM, so if we need fan to work |
| * then don't let EC enter LPM. |
| */ |
| #define TACH_FREQ EC_FREQ |
| |
| struct tach_it8xxx2_config { |
| /* Fan x tachometer LSB reading register */ |
| uintptr_t reg_fxtlrr; |
| /* Fan x tachometer MSB reading register */ |
| uintptr_t reg_fxtmrr; |
| /* Tachometer switch control register */ |
| uintptr_t reg_tswctlr; |
| /* Tachometer data valid bit of tswctlr register */ |
| int dvs_bit; |
| /* Tachometer data valid status bit of tswctlr register */ |
| int chsel_bit; |
| /* Tachometer alternate configuration */ |
| const struct pinctrl_dev_config *pcfg; |
| /* Select channel of tachometer */ |
| int channel; |
| /* Number of pulses per round of tachometer's input */ |
| int pulses_per_round; |
| }; |
| |
| /* Driver data */ |
| struct tach_it8xxx2_data { |
| /* Captured counts of tachometer */ |
| uint32_t capture; |
| }; |
| |
| static bool tach_ch_is_valid(const struct device *dev, int tach_ch) |
| { |
| const struct tach_it8xxx2_config *const config = dev->config; |
| volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr; |
| int dvs_bit = config->dvs_bit; |
| int chsel_bit = config->chsel_bit; |
| int mask = (dvs_bit | chsel_bit); |
| bool valid = false; |
| |
| switch (tach_ch) { |
| case IT8XXX2_TACH_CHANNEL_A: |
| if ((*reg_tswctlr & mask) == dvs_bit) { |
| valid = true; |
| } |
| break; |
| case IT8XXX2_TACH_CHANNEL_B: |
| if ((*reg_tswctlr & mask) == mask) { |
| valid = true; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return valid; |
| } |
| |
| static int tach_it8xxx2_sample_fetch(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| const struct tach_it8xxx2_config *const config = dev->config; |
| volatile uint8_t *reg_fxtlrr = (uint8_t *)config->reg_fxtlrr; |
| volatile uint8_t *reg_fxtmrr = (uint8_t *)config->reg_fxtmrr; |
| volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr; |
| int tach_ch = config->channel; |
| struct tach_it8xxx2_data *const data = dev->data; |
| |
| if ((chan != SENSOR_CHAN_RPM) && (chan != SENSOR_CHAN_ALL)) { |
| return -ENOTSUP; |
| } |
| |
| if (tach_ch_is_valid(dev, tach_ch)) { |
| /* If channel data of tachometer is valid, then save it */ |
| data->capture = ((*reg_fxtmrr) << 8) | (*reg_fxtlrr); |
| /* Clear tachometer data valid status */ |
| *reg_tswctlr |= config->dvs_bit; |
| } else { |
| /* If channel data of tachometer isn't valid, then clear it */ |
| data->capture = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int tach_it8xxx2_channel_get(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| const struct tach_it8xxx2_config *const config = dev->config; |
| int tachx = ((config->dvs_bit) == IT8XXX2_PWM_T0DVS) ? 0 : 1; |
| int p = config->pulses_per_round; |
| struct tach_it8xxx2_data *const data = dev->data; |
| |
| if (chan != SENSOR_CHAN_RPM) { |
| LOG_ERR("Sensor chan %d, only support SENSOR_CHAN_RPM", chan); |
| return -ENOTSUP; |
| } |
| |
| /* Transform count unit to RPM */ |
| if (data->capture > 0) { |
| if (tachx == 0) { |
| /* |
| * Fan Speed (RPM) = 60 / (1/fs * {F1TMRR, F1TLRR} * P) |
| * - P denotes the numbers of pulses per round |
| * - {F1TMRR, F1TLRR} = 0000h denotes Fan Speed is zero |
| * - The sampling rate (fs) is TACH_FREQ / 128 |
| */ |
| val->val1 = (60 * TACH_FREQ / 128 / p / (data->capture)); |
| } else { |
| /* |
| * Fan Speed (RPM) = {F2TMRR, F2TLRR} * 60 / P |
| * - P denotes the numbers of pulses per round |
| * - {F2TMRR, F2TLRR} = 0000h denotes Fan Speed is zero |
| */ |
| val->val1 = ((data->capture) * 120 / (p * 2)); |
| } |
| } else { |
| val->val1 = 0U; |
| } |
| |
| val->val2 = 0U; |
| |
| return 0; |
| } |
| |
| static int tach_it8xxx2_init(const struct device *dev) |
| { |
| const struct tach_it8xxx2_config *const config = dev->config; |
| volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr; |
| int tach_ch = config->channel; |
| int status; |
| |
| if (tach_ch > IT8XXX2_TACH_CHANNEL_B) { |
| LOG_ERR("Tach channel %d, only support 0 or 1", tach_ch); |
| return -EINVAL; |
| } |
| |
| /* Select pin to alternate mode for tachometer */ |
| status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (status < 0) { |
| LOG_ERR("Failed to configure TACH pins"); |
| return status; |
| } |
| |
| if (tach_ch == IT8XXX2_TACH_CHANNEL_A) { |
| /* Select IT8XXX2_TACH_CHANNEL_A output to tachometer */ |
| *reg_tswctlr &= ~(config->chsel_bit); |
| /* Clear tachometer data valid status */ |
| *reg_tswctlr |= config->dvs_bit; |
| } else { |
| /* Select IT8XXX2_TACH_CHANNEL_B output to tachometer */ |
| *reg_tswctlr |= config->chsel_bit; |
| /* Clear tachometer data valid status */ |
| *reg_tswctlr |= config->dvs_bit; |
| } |
| |
| /* Tachometer sensor already start */ |
| return 0; |
| } |
| |
| static const struct sensor_driver_api tach_it8xxx2_driver_api = { |
| .sample_fetch = tach_it8xxx2_sample_fetch, |
| .channel_get = tach_it8xxx2_channel_get, |
| }; |
| |
| #define TACH_IT8XXX2_INIT(inst) \ |
| PINCTRL_DT_INST_DEFINE(inst); \ |
| \ |
| static const struct tach_it8xxx2_config tach_it8xxx2_cfg_##inst = { \ |
| .reg_fxtlrr = DT_INST_REG_ADDR_BY_IDX(inst, 0), \ |
| .reg_fxtmrr = DT_INST_REG_ADDR_BY_IDX(inst, 1), \ |
| .reg_tswctlr = DT_INST_REG_ADDR_BY_IDX(inst, 2), \ |
| .dvs_bit = DT_INST_PROP(inst, dvs_bit), \ |
| .chsel_bit = DT_INST_PROP(inst, chsel_bit), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ |
| .channel = DT_INST_PROP(inst, channel), \ |
| .pulses_per_round = DT_INST_PROP(inst, pulses_per_round), \ |
| }; \ |
| \ |
| static struct tach_it8xxx2_data tach_it8xxx2_data_##inst; \ |
| \ |
| SENSOR_DEVICE_DT_INST_DEFINE(inst, \ |
| tach_it8xxx2_init, \ |
| NULL, \ |
| &tach_it8xxx2_data_##inst, \ |
| &tach_it8xxx2_cfg_##inst, \ |
| POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &tach_it8xxx2_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(TACH_IT8XXX2_INIT) |