| // SPDX-License-Identifier: GPL-2.0 |
| /** |
| * Driver for Schumacher (Photeon ps13216 Solid-State Relay) |
| * |
| * The driver implementation supports only ps13216 operating under Gang Mode. The IC should be |
| * pre-configured to Gang Mode by programming the CTRL.GANG eFuse bit during production. |
| * |
| * Copyright 2021 Google LLC. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/i2c.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/regmap.h> |
| #include <linux/ssr/ps13216_regs.h> |
| #include <linux/workqueue.h> |
| |
| #include "ps13216.h" |
| #include "ssr-core.h" |
| |
| /** |
| * Misc constants |
| */ |
| |
| // It takes 34 ms to go from OFF state to RUN state |
| #define PS13216_IF_EN_STARTUP_DELAY_MS 34 |
| |
| // It takes at least 128 ms to fill the FIFO memory bank. For some cases such as switch 0, |
| // however, the first ~30 samples are always invalid (zero value). Hence we keep capture |
| // 30 ms to overwrite the initial samples. |
| #define PS13216_HVSENSE_ADC_CAM_DELAY_MS 158 |
| #define PS13216_ADC_FIFO_BANK_BYTES 256 |
| |
| // Delay value for charge pumps. ADC readings |
| // taken before the charge pumps have fully |
| // charged will be inaccurate. |
| #define PS13216_CHARGE_PUMP_DELAY_MS 30 |
| |
| #define DEFAULT_HV_BUCK_DISABLE_DURATION 250 |
| |
| // Default value for duration from user space. |
| #define DEFAULT_HV_BUCK_DISABLE_DURATION_USER 250 |
| |
| // Each retry attempt for SSR close takes about 32ms. Limit attempts to 10, so |
| // the maximum delay is ~300ms. |
| #define PS13216_SSR_CLOSE_MAX_RETRY 10 |
| |
| enum ssr_state { |
| SSR_OPEN, |
| SSR_WAITING_TO_CLOSE, |
| SSR_CLOSED, |
| }; |
| |
| struct ps13216_device { |
| struct device *ssr_dev; |
| struct i2c_client *client; |
| struct gpio_desc *enable_gpio; |
| struct gpio_desc *hv_buck_gpio; |
| enum ssr_state relay_open_state; |
| struct regmap *regmap; |
| struct delayed_work enable_hv_buck_work; |
| u8 adc_fifo_data[PS13216_ADC_FIFO_BANK_BYTES]; |
| int hv_buck_disable_duration; |
| // True if blocked for SSR use. HV buck not allowed to control from user space. |
| bool hv_buck_blocked_by_ssr; |
| int hv_buck_disable_duration_user; |
| int ssr_close_retry_count; |
| }; |
| |
| static DEFINE_MUTEX(i2c_adc_lock); |
| static DEFINE_MUTEX(sysfs_hv_buck_disable_lock); |
| static DEFINE_MUTEX(hv_buck_lock); |
| |
| static int ps13216_check_internal_error(struct ps13216_device *ps) |
| { |
| u8 global_event; |
| |
| regmap_bulk_read(ps->regmap, PS13216_REG_GLOBAL_EVENTS, (u8 *)&global_event, 1); |
| if (global_event & |
| (PS13216_M_GLOBAL_EVENTS_LV2HV0 | PS13216_M_GLOBAL_EVENTS_LV2HV1)) { |
| dev_warn(&ps->client->dev, |
| "internal communication error. global_event reg: %d\n", |
| global_event); |
| return -EREMOTEIO; |
| } |
| return 0; |
| } |
| |
| static void ps13216_reset_events(struct ps13216_device *ps) |
| { |
| regmap_write(ps->regmap, PS13216_REG_EVENT0, PS13216_M_EVENT0_ALL); |
| regmap_write(ps->regmap, PS13216_REG_EVENT1, PS13216_M_EVENT1_ALL); |
| regmap_write(ps->regmap, PS13216_REG_GLOBAL_EVENTS, PS13216_M_GLOBAL_EVENTS_ALL); |
| regmap_write(ps->regmap, PS13216_REG_EVENT0_MASK, (u8)~(PS13216_M_EVENT0_MASK_ALL)); |
| regmap_write(ps->regmap, PS13216_REG_EVENT1_MASK, (u8)~(PS13216_M_EVENT1_MASK_ALL)); |
| } |
| |
| static void ps13216_relay_open(struct ps13216_device *ps, bool relay_open) |
| { |
| if (!relay_open && !ps->ssr_close_retry_count) { |
| dev_err(ps->ssr_dev, |
| "%s: maximum retries reached. SSR failed to close.", |
| __func__); |
| return; |
| } |
| |
| if (relay_open) { |
| regmap_write(ps->regmap, PS13216_REG_BB0XB, PS13216_M_BB0XB_C0); |
| ps->relay_open_state = SSR_OPEN; |
| dev_info(ps->ssr_dev, "%s: ssr opened!\n", __func__); |
| |
| if (ps->hv_buck_gpio) { |
| mutex_lock(&sysfs_hv_buck_disable_lock); |
| ps->hv_buck_blocked_by_ssr = true; |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| |
| mutex_lock(&hv_buck_lock); |
| cancel_delayed_work_sync(&ps->enable_hv_buck_work); |
| schedule_delayed_work(&ps->enable_hv_buck_work, |
| msecs_to_jiffies(ps->hv_buck_disable_duration)); |
| mutex_unlock(&hv_buck_lock); |
| } |
| } else { |
| --ps->ssr_close_retry_count; |
| |
| // Reset event registers |
| ps13216_reset_events(ps); |
| |
| // Enable force active and wait for charge pumps to charge |
| regmap_write(ps->regmap, PS13216_REG_FORCE_ACTIVE, |
| PS13216_V_FORCE_ACTIVE(1)); |
| msleep(PS13216_CHARGE_PUMP_DELAY_MS); |
| |
| // Enter SW test mode |
| regmap_write(ps->regmap, PS13216_REG_SW_TEST_MODE, |
| PS13216_ENTER_TEST_MODE_1ST_BYTE); |
| regmap_write(ps->regmap, PS13216_REG_SW_TEST_MODE, |
| PS13216_ENTER_TEST_MODE_2ND_BYTE); |
| // Config Signal Spy on Channel 0 Zero-crossing comparator |
| regmap_write(ps->regmap, PS13216_REG_TEST_SIGNAL_SPY, |
| PS13216_SIGNALSPY_ZERO_CROSS_CH0); |
| // Set Fast Mode control for Channel 0 |
| regmap_write(ps->regmap, PS13216_REG_SETUP, |
| PS13216_V_SETUP_FAST_MODE_CONFIG(PS13216_SETUP_FAST_MODE_CH0_CH1)); |
| |
| // Relay state must be updated after setting "Signal Spy" on |
| // zero-crossing comparator in order to avoid handling the |
| // unwanted interrupt introduced when switching to signal spy |
| // mode. |
| ps->relay_open_state = SSR_WAITING_TO_CLOSE; |
| } |
| } |
| |
| static void enable_hv_buck_handler(struct work_struct *work) |
| { |
| struct ps13216_device* ps = container_of( |
| work, struct ps13216_device, enable_hv_buck_work.work); |
| |
| if (ps->hv_buck_gpio) { |
| gpiod_set_value(ps->hv_buck_gpio, 0); |
| mutex_lock(&sysfs_hv_buck_disable_lock); |
| if (ps->hv_buck_blocked_by_ssr) { |
| dev_info(ps->ssr_dev, "%s: HV buck enabled", __func__); |
| ps->hv_buck_blocked_by_ssr = false; |
| } else { |
| dev_info(ps->ssr_dev, "%s: HV buck enabled for user", __func__); |
| } |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| } |
| } |
| |
| static irqreturn_t ps13216_irq_handler_thread(int irq, void *data) |
| { |
| struct ps13216_device *ps = data; |
| struct device *dev = &ps->client->dev; |
| |
| if (ps->relay_open_state == SSR_WAITING_TO_CLOSE) { |
| // Close SSR switch |
| regmap_write(ps->regmap, PS13216_REG_BB1XB, PS13216_M_BB1XB_C0); |
| ps->relay_open_state = SSR_CLOSED; |
| |
| // Leave SW test mode |
| regmap_write(ps->regmap, PS13216_REG_SW_TEST_MODE, |
| PS13216_LEAVE_TEST_MODE_1ST_BYTE); |
| regmap_write(ps->regmap, PS13216_REG_SW_TEST_MODE, |
| PS13216_LEAVE_TEST_MODE_2ND_BYTE); |
| // Reset signal spy |
| regmap_write(ps->regmap, PS13216_REG_TEST_SIGNAL_SPY, |
| PS13216_SIGNALSPY_DISABLE); |
| |
| if (ps13216_check_internal_error(ps)) { |
| dev_warn(dev, "%s: ssr failed to close, retrying!\n", __func__); |
| ps13216_relay_open(ps, false); |
| return IRQ_HANDLED; |
| } |
| |
| dev_info(dev, "%s: ssr closed!\n", __func__); |
| |
| if (ps->hv_buck_gpio) { |
| mutex_lock(&sysfs_hv_buck_disable_lock); |
| ps->hv_buck_blocked_by_ssr = true; |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| |
| mutex_lock(&hv_buck_lock); |
| cancel_delayed_work_sync(&ps->enable_hv_buck_work); |
| gpiod_set_value(ps->hv_buck_gpio, 1); |
| dev_info(ps->ssr_dev, "%s: HV buck disabled", __func__); |
| mutex_unlock(&hv_buck_lock); |
| } |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static ssize_t ssr_relay_open_store(struct device *dev, const char *buf, size_t count) { |
| int ret = 0; |
| struct ps13216_device *ps = dev_get_drvdata(dev); |
| bool relay_open; |
| |
| ret = kstrtobool(buf, &relay_open); |
| if (ret < 0) |
| return ret; |
| |
| ps->ssr_close_retry_count = PS13216_SSR_CLOSE_MAX_RETRY; |
| |
| ps13216_relay_open(ps, relay_open); |
| |
| return count; |
| } |
| |
| static ssize_t ssr_relay_state_show(struct device *dev, char *buf) { |
| struct ps13216_device *ps = dev_get_drvdata(dev); |
| |
| if (ps->relay_open_state == SSR_OPEN) { |
| return scnprintf(buf, PAGE_SIZE, "open\n"); |
| } else if (ps->relay_open_state == SSR_WAITING_TO_CLOSE) { |
| return scnprintf(buf, PAGE_SIZE, "closing\n"); |
| } else { // SSR_CLOSED |
| return scnprintf(buf, PAGE_SIZE, "closed\n"); |
| } |
| } |
| |
| /** |
| * ssr_voltage_show() - Read the voltage from a specific channel |
| * @dev: The device structure of SSR |
| * @channel: The bit of the channel to be read |
| * @buf: The buffer for the voltage data |
| * |
| * Return: The number of characters written into buf. |
| */ |
| static ssize_t ssr_voltage_show(struct device *dev, unsigned int channel, char *buf) { |
| struct ps13216_device *ps = dev_get_drvdata(dev); |
| u16 fifo_len; |
| u8 *fifo_data_ptr; |
| size_t sample_count; |
| size_t invalid_sample_count; |
| size_t idx; |
| int raw_voltage; |
| int mean_squared_voltage; |
| int ret; |
| |
| mutex_lock(&i2c_adc_lock); |
| // Clear ADC EOC (End of Conversion) event flag. |
| regmap_write(ps->regmap, PS13216_REG_EVENT1, PS13216_M_EVENT1_EOC); |
| |
| regmap_write(ps->regmap, PS13216_REG_ADC_CTRL, |
| PS13216_V_ADC_CTRL_MODE(PS13216_ADC_MODE_CAM) | channel); |
| msleep(PS13216_HVSENSE_ADC_CAM_DELAY_MS); |
| |
| regmap_write(ps->regmap, PS13216_REG_ADC_CTRL, 0x00); |
| |
| // Read FIFO length |
| regmap_bulk_read(ps->regmap, PS13216_REG_ADC_FIFO_FILL_CNT_LSB, (u8 *)&fifo_len, 2); |
| |
| ret = regmap_noinc_read(ps->regmap, PS13216_REG_ADC_FIFO, ps->adc_fifo_data, fifo_len); |
| mutex_unlock(&i2c_adc_lock); |
| if (ret < 0) |
| return ret; |
| |
| // Each voltage read takes 2 bytes |
| sample_count = fifo_len / 2; |
| fifo_data_ptr = ps->adc_fifo_data; |
| mean_squared_voltage = 0; |
| invalid_sample_count = 0; |
| |
| for (idx = 0; idx < sample_count; fifo_data_ptr += 2, ++idx) { |
| // Only 10 bits are required to decode the voltage data. |
| raw_voltage = (int)((fifo_data_ptr[1] & 3) << 8) | fifo_data_ptr[0]; |
| |
| // Due to unknown reasons, the driver might return some empty values (0). |
| // Skip then when calculating mean squared value. |
| if (raw_voltage == 0) { |
| invalid_sample_count++; |
| continue; |
| } |
| |
| // For switch voltage monitoring, should substract 512 from the raw value. |
| if (channel == PS13216_M_ADC_CTRL_VI0 || channel == PS13216_M_ADC_CTRL_VI1) |
| raw_voltage -= 512; |
| mean_squared_voltage += raw_voltage * raw_voltage; |
| } |
| if (invalid_sample_count > 0) |
| dev_warn(dev, "%s: found %d invalid samples", __func__, invalid_sample_count); |
| |
| if (sample_count == invalid_sample_count) { |
| dev_err(dev, "%s: no valid samples collected", __func__); |
| return scnprintf(buf, PAGE_SIZE, "0\n"); |
| } |
| |
| mean_squared_voltage /= (sample_count - invalid_sample_count); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", mean_squared_voltage); |
| } |
| |
| static ssize_t ssr_hvsense_voltage_show(struct device *dev, char *buf) { |
| return ssr_voltage_show(dev, PS13216_M_ADC_CTRL_HV_SENSE, buf); |
| } |
| |
| static ssize_t ssr_acin1_voltage_show(struct device *dev, char *buf) { |
| return ssr_voltage_show(dev, PS13216_M_ADC_CTRL_VI0, buf); |
| } |
| |
| static ssize_t ssr_acin2_voltage_show(struct device *dev, char *buf) { |
| return ssr_voltage_show(dev, PS13216_M_ADC_CTRL_VI1, buf); |
| } |
| |
| static const struct ssr_ops ssr_ops = { |
| .relay_open_store = ssr_relay_open_store, |
| .relay_state_show = ssr_relay_state_show, |
| .hv_vdd_show = ssr_hvsense_voltage_show, |
| .acin1_vdd_show = ssr_acin1_voltage_show, |
| .acin2_vdd_show = ssr_acin2_voltage_show, |
| }; |
| |
| static ssize_t hv_buck_duration_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ps13216_device *ps = dev_get_drvdata(dev->parent); |
| return scnprintf(buf, PAGE_SIZE, "%d\n", ps->hv_buck_disable_duration); |
| } |
| |
| static ssize_t hv_buck_duration_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t len) |
| { |
| struct ps13216_device *ps = dev_get_drvdata(dev->parent); |
| |
| int duration; |
| if (kstrtoint(buf, 0, &duration)) |
| return -EINVAL; |
| |
| ps->hv_buck_disable_duration = duration; |
| return len; |
| } |
| |
| static DEVICE_ATTR(hv_buck_duration, 0644, |
| hv_buck_duration_show, hv_buck_duration_store); |
| |
| static ssize_t hv_buck_disable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ps13216_device *ps = dev_get_drvdata(dev->parent); |
| |
| return scnprintf(buf, PAGE_SIZE, "%d\n", ps->hv_buck_disable_duration_user); |
| } |
| |
| static ssize_t hv_buck_disable_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t len) |
| { |
| int ret; |
| int hv_buck_disable_duration_user; |
| struct ps13216_device *ps = dev_get_drvdata(dev->parent); |
| |
| if (!ps->hv_buck_gpio) |
| return -EIO; |
| |
| mutex_lock(&sysfs_hv_buck_disable_lock); |
| if (ps->hv_buck_blocked_by_ssr) { |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| return -EBUSY; |
| } |
| |
| ret = kstrtoint(buf, 0, &hv_buck_disable_duration_user); |
| if (ret < 0 || hv_buck_disable_duration_user <= 0) { |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| return -EINVAL; |
| } |
| |
| // Disable HV buck now. |
| gpiod_set_value(ps->hv_buck_gpio, 1); |
| dev_info(ps->ssr_dev, "%s: HV buck disabled by user", __func__); |
| |
| // Delayed work to re-enable HV buck. |
| ps->hv_buck_disable_duration_user = hv_buck_disable_duration_user; |
| schedule_delayed_work(&ps->enable_hv_buck_work, |
| msecs_to_jiffies(ps->hv_buck_disable_duration_user)); |
| |
| mutex_unlock(&sysfs_hv_buck_disable_lock); |
| return len; |
| } |
| |
| static DEVICE_ATTR_RW(hv_buck_disable); |
| |
| static struct attribute *ps13216_attributes[] = { |
| &dev_attr_hv_buck_duration.attr, |
| &dev_attr_hv_buck_disable.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group ps13216_attr_group = { |
| .attrs = ps13216_attributes |
| }; |
| |
| static int ps13216_probe(struct i2c_client *client, const struct i2c_device_id *id) { |
| struct device *dev = &client->dev; |
| struct ps13216_device *ps; |
| int ret; |
| |
| ps = devm_kmalloc(dev, sizeof(struct ps13216_device), GFP_KERNEL); |
| if (!ps) { |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| ps->client = client; |
| ps->relay_open_state = SSR_OPEN; |
| ps->hv_buck_disable_duration = DEFAULT_HV_BUCK_DISABLE_DURATION; |
| ps->hv_buck_blocked_by_ssr = false; |
| ps->hv_buck_disable_duration_user = DEFAULT_HV_BUCK_DISABLE_DURATION_USER; |
| |
| if (client->irq) { |
| ret = devm_request_threaded_irq(dev, client->irq, NULL, |
| ps13216_irq_handler_thread, |
| IRQF_ONESHOT, |
| dev_name(dev), ps); |
| if (ret < 0) { |
| dev_err(dev, "get irq fail: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| // Initialize SSR_EN gpio and enable Schumacher |
| ps->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH); |
| if (IS_ERR(ps->enable_gpio)) { |
| dev_err(dev, "%s: failed to initialize enable_gpio.\n", __func__); |
| ret = PTR_ERR(ps->enable_gpio); |
| goto error; |
| } |
| msleep(PS13216_IF_EN_STARTUP_DELAY_MS); |
| |
| ps->hv_buck_gpio = devm_gpiod_get(dev, "hvbuck", GPIOD_OUT_LOW); |
| if (IS_ERR(ps->hv_buck_gpio)) { |
| dev_err(dev, "%s: failed to initialize hv_buck_gpio, ignored\n", __func__); |
| ps->hv_buck_gpio = NULL; |
| } else { |
| gpiod_direction_output(ps->hv_buck_gpio, 0); |
| } |
| |
| i2c_set_clientdata(client, ps); |
| |
| ps->regmap = devm_regmap_init_i2c(client, &ps13216_regmap_config); |
| if (IS_ERR(ps->regmap)) { |
| ret = PTR_ERR(ps->regmap); |
| dev_err(dev, "%s: failed to allocate register map: %d\n", __func__, ret); |
| goto error; |
| } |
| |
| ps->ssr_dev = ssr_device_register(dev, &ssr_ops); |
| if (IS_ERR(ps->ssr_dev)) { |
| ret = PTR_ERR(ps->ssr_dev); |
| goto error; |
| } |
| |
| dev_info(dev, "%s: %s probe successfully!\n", __func__, client->name); |
| |
| ret = sysfs_create_group(&ps->ssr_dev->kobj, &ps13216_attr_group); |
| if (ret) { |
| dev_err(dev, "ssr sysfs rc: %d\n", ret); |
| goto error; |
| } |
| |
| INIT_DELAYED_WORK(&ps->enable_hv_buck_work, enable_hv_buck_handler); |
| |
| return 0; |
| |
| error: |
| dev_err(dev, "%s: error %d\n", __func__, ret); |
| return ret; |
| } |
| |
| static int ps13216_remove(struct i2c_client *client) { |
| struct ps13216_device *ps = i2c_get_clientdata(client); |
| |
| ssr_device_unregister(ps->ssr_dev); |
| return 0; |
| } |
| |
| static const struct i2c_device_id ps13216_id[] = { |
| { "ps13216-ssr", 0 }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(i2c, ps13216_id); |
| |
| static struct of_device_id ps13216_of_match[] = { |
| { |
| .compatible = "photeon,ps13216", |
| }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, ps13216_of_match); |
| |
| static struct i2c_driver ps13216_driver = { |
| .driver = { |
| .name = "ps13216-ssr", |
| .of_match_table = ps13216_of_match, |
| }, |
| .probe = ps13216_probe, |
| .remove = ps13216_remove, |
| .id_table = ps13216_id, |
| }; |
| module_i2c_driver(ps13216_driver); |
| |
| MODULE_AUTHOR("Paul Wang <tzupengwang@google.com>"); |
| MODULE_DESCRIPTION("PS13216 SSR driver"); |
| MODULE_LICENSE("GPL v2"); |