/* * Khadas Edge2 MCU control driver * * Written by: Nick * * Copyright (C) 2022 Wesion Technologies Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include /* Device registers */ #define MCU_PWR_OFF_CMD_REG 0x80 #define MCU_SHUTDOWN_NORMAL_REG 0x2c /*Fan device*/ #define MCU_CMD_FAN_STATUS_CTRL_REGv2 0x8A #define MCU_FAN_TRIG_TEMP_LEVEL0 60 // 50 degree if not set #define MCU_FAN_TRIG_TEMP_LEVEL1 80 // 60 degree if not set #define MCU_FAN_TRIG_TEMP_LEVEL2 100 // 70 degree if not set #define MCU_FAN_TRIG_MAXTEMP 105 #define MCU_FAN_LOOP_SECS (30 * HZ) // 30 seconds #define MCU_FAN_TEST_LOOP_SECS (5 * HZ) // 5 seconds #define MCU_FAN_LOOP_NODELAY_SECS 0 #define MCU_FAN_SPEED_OFF 0 #define MCU_FAN_SPEED_LOW 1 #define MCU_FAN_SPEED_MID 2 #define MCU_FAN_SPEED_HIGH 3 #define MCU_FAN_SPEED_LOW_V2 0x32 #define MCU_FAN_SPEED_MID_V2 0x48 #define MCU_FAN_SPEED_HIGH_V2 0x64 enum khadas_board { KHADAS_BOARD_NONE = 0, KHADAS_BOARD_EDGE2 }; enum khadas_board_hwver { KHADAS_BOARD_HWVER_NONE = 0, KHADAS_BOARD_HWVER_V11 }; enum mcu_fan_mode { MCU_FAN_MODE_MANUAL = 0, MCU_FAN_MODE_AUTO }; enum mcu_fan_level { MCU_FAN_LEVEL_0 = 0, MCU_FAN_LEVEL_1, MCU_FAN_LEVEL_2, MCU_FAN_LEVEL_3 }; enum mcu_fan_status { MCU_FAN_STATUS_DISABLE = 0, MCU_FAN_STATUS_ENABLE, }; struct mcu_fan_data { struct platform_device *pdev; struct class *fan_class; struct delayed_work work; struct delayed_work fan_test_work; enum mcu_fan_status enable; enum mcu_fan_mode mode; enum mcu_fan_level level; int trig_temp_level0; int trig_temp_level1; int trig_temp_level2; }; struct mcu_data { struct i2c_client *client; struct class *mcu_class; enum khadas_board board; enum khadas_board_hwver hwver; struct mcu_fan_data fan_data; }; struct mcu_data *g_mcu_data; static int i2c_master_reg8_send(const struct i2c_client *client, const char reg, const char *buf, int count) { struct i2c_adapter *adap = client->adapter; struct i2c_msg msg; int ret; char *tx_buf = kzalloc(count + 1, GFP_KERNEL); if (!tx_buf) return -ENOMEM; tx_buf[0] = reg; memcpy(tx_buf+1, buf, count); msg.addr = client->addr; msg.flags = client->flags; msg.len = count + 1; msg.buf = (char *)tx_buf; ret = i2c_transfer(adap, &msg, 1); kfree(tx_buf); return (ret == 1) ? count : ret; } static int i2c_master_reg8_recv(const struct i2c_client *client, const char reg, char *buf, int count) { struct i2c_adapter *adap = client->adapter; struct i2c_msg msgs[2]; int ret; char reg_buf = reg; msgs[0].addr = client->addr; msgs[0].flags = client->flags; msgs[0].len = 1; msgs[0].buf = ®_buf; msgs[1].addr = client->addr; msgs[1].flags = client->flags | I2C_M_RD; msgs[1].len = count; msgs[1].buf = (char *)buf; ret = i2c_transfer(adap, msgs, 2); return (ret == 2) ? count : ret; } static int mcu_i2c_read_regs(struct i2c_client *client, u8 reg, u8 buf[], unsigned len) { int ret; ret = i2c_master_reg8_recv(client, reg, buf, len); return ret; } static int mcu_i2c_write_regs(struct i2c_client *client, u8 reg, u8 const buf[], __u16 len) { int ret; ret = i2c_master_reg8_send(client, reg, buf, (int)len); return ret; } static int is_mcu_fan_control_supported(void) { // MCU FAN control is supported for: // 1. Khadas EDGE2 if (g_mcu_data->board == KHADAS_BOARD_EDGE2) { if (g_mcu_data->hwver >= KHADAS_BOARD_HWVER_V11) return 1; else return 0; } else { return 0; } } static void mcu_fan_level_set(struct mcu_fan_data *fan_data, int level) { if (is_mcu_fan_control_supported()) { int ret; u8 data = 0; g_mcu_data->fan_data.level = level; if (g_mcu_data->board == KHADAS_BOARD_EDGE2) { if (level == 0) data = MCU_FAN_SPEED_OFF; else if (level == 1) data = MCU_FAN_SPEED_LOW_V2; else if (level == 2) data = MCU_FAN_SPEED_MID_V2; else if (level == 3) data = MCU_FAN_SPEED_HIGH_V2; ret = mcu_i2c_write_regs(g_mcu_data->client, MCU_CMD_FAN_STATUS_CTRL_REGv2, &data, 1); if (ret < 0) { pr_debug("write fan control err\n"); return; } } } } extern int rk_get_temperature(void); static void fan_work_func(struct work_struct *_work) { if (is_mcu_fan_control_supported()) { int temp = -EINVAL; struct mcu_fan_data *fan_data = &g_mcu_data->fan_data; if (g_mcu_data->board == KHADAS_BOARD_EDGE2) { temp = rk_get_temperature(); } else { temp = fan_data->trig_temp_level0; } if (temp != -EINVAL) { if (temp < fan_data->trig_temp_level0) mcu_fan_level_set(fan_data, 0); else if (temp < fan_data->trig_temp_level1) mcu_fan_level_set(fan_data, 1); else if (temp < fan_data->trig_temp_level2) mcu_fan_level_set(fan_data, 2); else mcu_fan_level_set(fan_data, 3); } schedule_delayed_work(&fan_data->work, MCU_FAN_LOOP_SECS); } } static void khadas_fan_set(struct mcu_fan_data *fan_data) { if (is_mcu_fan_control_supported()) { cancel_delayed_work(&fan_data->work); if (fan_data->enable == MCU_FAN_STATUS_DISABLE) { mcu_fan_level_set(fan_data, 0); return; } switch (fan_data->mode) { case MCU_FAN_MODE_MANUAL: switch (fan_data->level) { case MCU_FAN_LEVEL_0: mcu_fan_level_set(fan_data, 0); break; case MCU_FAN_LEVEL_1: mcu_fan_level_set(fan_data, 1); break; case MCU_FAN_LEVEL_2: mcu_fan_level_set(fan_data, 2); break; case MCU_FAN_LEVEL_3: mcu_fan_level_set(fan_data, 3); break; default: break; } break; case MCU_FAN_MODE_AUTO: // FIXME: achieve with a better way schedule_delayed_work(&fan_data->work, MCU_FAN_LOOP_NODELAY_SECS); break; default: break; } } } static ssize_t show_fan_enable(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan enable: %d\n", g_mcu_data->fan_data.enable); } static ssize_t store_fan_enable(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int enable; if (kstrtoint(buf, 0, &enable)) return -EINVAL; // 0: manual, 1: auto if (enable >= 0 && enable < 2) { g_mcu_data->fan_data.enable = enable; khadas_fan_set(&g_mcu_data->fan_data); } return count; } static ssize_t show_fan_mode(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan mode: %d\n", g_mcu_data->fan_data.mode); } static ssize_t store_fan_mode(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int mode; if (kstrtoint(buf, 0, &mode)) return -EINVAL; // 0: manual, 1: auto if (mode >= 0 && mode < 2) { g_mcu_data->fan_data.mode = mode; khadas_fan_set(&g_mcu_data->fan_data); } return count; } static ssize_t show_fan_level(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan level: %d\n", g_mcu_data->fan_data.level); } static ssize_t store_fan_level(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int level; if (kstrtoint(buf, 0, &level)) return -EINVAL; if (level >= 0 && level < 4) { g_mcu_data->fan_data.level = level; khadas_fan_set(&g_mcu_data->fan_data); } return count; } static ssize_t show_fan_temp(struct class *cls, struct class_attribute *attr, char *buf) { struct mcu_fan_data *fan_data = &g_mcu_data->fan_data; int temp = -EINVAL; if (g_mcu_data->board == KHADAS_BOARD_EDGE2) temp = rk_get_temperature(); else temp = fan_data->trig_temp_level0; return sprintf(buf, "cpu_temp:%d\nFan trigger temperature: level0:%d level1:%d level2:%d\n", temp, g_mcu_data->fan_data.trig_temp_level0, g_mcu_data->fan_data.trig_temp_level1, g_mcu_data->fan_data.trig_temp_level2); } void fan_level_set(struct mcu_data *ug_mcu_data) { struct mcu_fan_data *fan_data = &g_mcu_data->fan_data; int temp = -EINVAL; if (ug_mcu_data->board == KHADAS_BOARD_EDGE2) temp = rk_get_temperature(); else temp = fan_data->trig_temp_level0; if (temp != -EINVAL) { if (temp < ug_mcu_data->fan_data.trig_temp_level0) mcu_fan_level_set(fan_data, 0); else if (temp < ug_mcu_data->fan_data.trig_temp_level1) mcu_fan_level_set(fan_data, 1); else if (temp < ug_mcu_data->fan_data.trig_temp_level2) mcu_fan_level_set(fan_data, 2); else mcu_fan_level_set(fan_data, 3); } } static ssize_t show_fan_trigger_low(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan trigger low speed temperature:%d\n", g_mcu_data->fan_data.trig_temp_level0); } static ssize_t store_fan_trigger_low(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int trigger; if (kstrtoint(buf, 0, &trigger)) return -EINVAL; if (trigger >= g_mcu_data->fan_data.trig_temp_level1) { pr_err("Invalid parameter\n"); return -EINVAL; } g_mcu_data->fan_data.trig_temp_level0 = trigger; fan_level_set(g_mcu_data); return count; } static ssize_t show_fan_trigger_mid(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan trigger mid speed temperature:%d\n", g_mcu_data->fan_data.trig_temp_level1); } static ssize_t store_fan_trigger_mid(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int trigger; if (kstrtoint(buf, 0, &trigger)) return -EINVAL; if (trigger >= g_mcu_data->fan_data.trig_temp_level2 || trigger <= g_mcu_data->fan_data.trig_temp_level0){ pr_err("Invalid parameter\n"); return -EINVAL; } g_mcu_data->fan_data.trig_temp_level1 = trigger; fan_level_set(g_mcu_data); return count; } static ssize_t show_fan_trigger_high(struct class *cls, struct class_attribute *attr, char *buf) { return sprintf(buf, "Fan trigger high speed temperature:%d\n", g_mcu_data->fan_data.trig_temp_level2); } static ssize_t store_fan_trigger_high(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { int trigger; if (kstrtoint(buf, 0, &trigger)) return -EINVAL; if (trigger <= g_mcu_data->fan_data.trig_temp_level1) { pr_err("Invalid parameter\n"); return -EINVAL; } g_mcu_data->fan_data.trig_temp_level2 = trigger; fan_level_set(g_mcu_data); return count; } static ssize_t store_mcu_poweroff(struct class *cls,struct class_attribute *attr, const char *buf, size_t count) { int ret; int val; char reg; if (kstrtoint(buf, 0, &val)) return -EINVAL; if (val != 1) return -EINVAL; reg = (char)val; ret = mcu_i2c_write_regs(g_mcu_data->client,MCU_PWR_OFF_CMD_REG, ®, 1); if (ret < 0) { pr_debug("write poweroff cmd error\n"); return ret; } return count; } static ssize_t store_mcu_rst(struct class *cls, struct class_attribute *attr, const char *buf, size_t count) { char reg; int ret; int rst; if (kstrtoint(buf, 0, &rst)) return -EINVAL; reg = rst; ret = mcu_i2c_write_regs(g_mcu_data->client,MCU_SHUTDOWN_NORMAL_REG, ®, 1); if (ret < 0) { pr_debug("write poweroff cmd error\n"); return ret; } return count; } static struct class_attribute fan_class_attrs[] = { __ATTR(enable, 0644, show_fan_enable, store_fan_enable), __ATTR(mode, 0644, show_fan_mode, store_fan_mode), __ATTR(level, 0644, show_fan_level, store_fan_level), __ATTR(trigger_temp_low, 0644, show_fan_trigger_low, store_fan_trigger_low), __ATTR(trigger_temp_mid, 0644, show_fan_trigger_mid, store_fan_trigger_mid), __ATTR(trigger_temp_high, 0644, show_fan_trigger_high, store_fan_trigger_high), __ATTR(temp, 0644, show_fan_temp, NULL), }; static struct class_attribute mcu_class_attrs[] = { __ATTR(poweroff, 0644, NULL, store_mcu_poweroff), __ATTR(rst, 0644, NULL, store_mcu_rst), }; static void create_mcu_attrs(void) { int i; g_mcu_data->mcu_class = class_create(THIS_MODULE, "mcu"); if (IS_ERR(g_mcu_data->mcu_class)) { pr_err("create mcu_class debug class fail\n"); return; } for (i = 0; i < ARRAY_SIZE(mcu_class_attrs); i++) { if (class_create_file(g_mcu_data->mcu_class, &mcu_class_attrs[i])) pr_err("create mcu attribute %s fail\n", mcu_class_attrs[i].attr.name); } if (is_mcu_fan_control_supported()) { g_mcu_data->fan_data.fan_class = class_create(THIS_MODULE, "fan"); if (IS_ERR(g_mcu_data->fan_data.fan_class)) { pr_err("create fan_class debug class fail\n"); return; } for (i = 0; i < ARRAY_SIZE(fan_class_attrs); i++) { if (class_create_file(g_mcu_data->fan_data.fan_class, &fan_class_attrs[i])) pr_err("create fan attribute %s fail\n", fan_class_attrs[i].attr.name); } } } static int mcu_parse_dt(struct device *dev) { int ret = 0; const char *hwver = NULL; if (NULL == dev) return -EINVAL; ret = of_property_read_string(dev->of_node, "hwver", &hwver); if (ret < 0) { return 0; } else { if (strstr(hwver, "EDGE2")) g_mcu_data->board = KHADAS_BOARD_EDGE2; else g_mcu_data->board = KHADAS_BOARD_NONE; if (g_mcu_data->board == KHADAS_BOARD_EDGE2) { if (strcmp(hwver, "EDGE2.V11") == 0) g_mcu_data->hwver = KHADAS_BOARD_HWVER_V11; } } ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level0", &g_mcu_data->fan_data.trig_temp_level0); if (ret < 0) g_mcu_data->fan_data.trig_temp_level0 = MCU_FAN_TRIG_TEMP_LEVEL0; ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level1", &g_mcu_data->fan_data.trig_temp_level1); if (ret < 0) g_mcu_data->fan_data.trig_temp_level1 = MCU_FAN_TRIG_TEMP_LEVEL1; ret = of_property_read_u32(dev->of_node, "fan,trig_temp_level2",&g_mcu_data->fan_data.trig_temp_level2); if (ret < 0){ g_mcu_data->fan_data.trig_temp_level2 =MCU_FAN_TRIG_TEMP_LEVEL2; } return ret; } static int mcu_probe(struct i2c_client *client, const struct i2c_device_id *id) { printk("%s\n", __func__); if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; g_mcu_data = kzalloc(sizeof(struct mcu_data), GFP_KERNEL); if (g_mcu_data == NULL) return -ENOMEM; mcu_parse_dt(&client->dev); g_mcu_data->client = client; g_mcu_data->fan_data.mode = MCU_FAN_MODE_AUTO; g_mcu_data->fan_data.level = MCU_FAN_LEVEL_0; g_mcu_data->fan_data.enable = MCU_FAN_STATUS_ENABLE; INIT_DELAYED_WORK(&g_mcu_data->fan_data.work, fan_work_func); mcu_fan_level_set(&g_mcu_data->fan_data, 0); schedule_delayed_work(&g_mcu_data->fan_data.work, MCU_FAN_LOOP_SECS); create_mcu_attrs(); return 0; } static int mcu_remove(struct i2c_client *client) { kfree(g_mcu_data); return 0; } static void khadas_fan_shutdown(struct i2c_client *client) { g_mcu_data->fan_data.enable = MCU_FAN_STATUS_DISABLE; khadas_fan_set(&g_mcu_data->fan_data); } #ifdef CONFIG_PM_SLEEP static int khadas_fan_suspend(struct device *dev) { cancel_delayed_work(&g_mcu_data->fan_data.work); mcu_fan_level_set(&g_mcu_data->fan_data, 0); return 0; } static int khadas_fan_resume(struct device *dev) { khadas_fan_set(&g_mcu_data->fan_data); return 0; } static const struct dev_pm_ops fan_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(khadas_fan_suspend, khadas_fan_resume) }; #define FAN_PM_OPS (&(fan_dev_pm_ops)) #endif static const struct i2c_device_id mcu_id[] = { { "khadas-mcu", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, mcu_id); static struct of_device_id mcu_dt_ids[] = { { .compatible = "khadas-mcu" }, {}, }; MODULE_DEVICE_TABLE(i2c, mcu_dt_ids); struct i2c_driver mcu_driver = { .driver = { .name = "khadas-mcu", .owner = THIS_MODULE, .of_match_table = of_match_ptr(mcu_dt_ids), #ifdef CONFIG_PM_SLEEP .pm = FAN_PM_OPS, #endif }, .probe = mcu_probe, .remove = mcu_remove, .shutdown = khadas_fan_shutdown, .id_table = mcu_id, }; module_i2c_driver(mcu_driver); MODULE_AUTHOR("Nick "); MODULE_DESCRIPTION("Khadas Edge2 MCU control driver"); MODULE_LICENSE("GPL");