// SPDX-License-Identifier: GPL-2.0 /* * Driver for Khadas System control Microcontroller * * Copyright (C) 2020 BayLibre SAS * * Author(s): Neil Armstrong <narmstrong@baylibre.com> */ #include <linux/bitfield.h> #include <linux/i2c.h> #include <linux/mfd/core.h> #include <linux/mfd/khadas-mcu.h> #include <linux/module.h> #include <linux/regmap.h> static bool khadas_mcu_reg_volatile(struct device *dev, unsigned int reg) { if (reg >= KHADAS_MCU_USER_DATA_0_REG && reg < KHADAS_MCU_PWR_OFF_CMD_REG) return true; switch (reg) { case KHADAS_MCU_PWR_OFF_CMD_REG: case KHADAS_MCU_PASSWD_START_REG: case KHADAS_MCU_CHECK_VEN_PASSWD_REG: case KHADAS_MCU_CHECK_USER_PASSWD_REG: case KHADAS_MCU_WOL_INIT_START_REG: case KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG: return true; default: return false; } } static bool khadas_mcu_reg_writeable(struct device *dev, unsigned int reg) { switch (reg) { case KHADAS_MCU_PASSWD_VEN_0_REG: case KHADAS_MCU_PASSWD_VEN_1_REG: case KHADAS_MCU_PASSWD_VEN_2_REG: case KHADAS_MCU_PASSWD_VEN_3_REG: case KHADAS_MCU_PASSWD_VEN_4_REG: case KHADAS_MCU_PASSWD_VEN_5_REG: case KHADAS_MCU_MAC_0_REG: case KHADAS_MCU_MAC_1_REG: case KHADAS_MCU_MAC_2_REG: case KHADAS_MCU_MAC_3_REG: case KHADAS_MCU_MAC_4_REG: case KHADAS_MCU_MAC_5_REG: case KHADAS_MCU_USID_0_REG: case KHADAS_MCU_USID_1_REG: case KHADAS_MCU_USID_2_REG: case KHADAS_MCU_USID_3_REG: case KHADAS_MCU_USID_4_REG: case KHADAS_MCU_USID_5_REG: case KHADAS_MCU_VERSION_0_REG: case KHADAS_MCU_VERSION_1_REG: case KHADAS_MCU_DEVICE_NO_0_REG: case KHADAS_MCU_DEVICE_NO_1_REG: case KHADAS_MCU_FACTORY_TEST_REG: case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG: return false; default: return true; } } static const struct regmap_config khadas_mcu_regmap_config = { .reg_bits = 8, .reg_stride = 1, .val_bits = 8, .max_register = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, .volatile_reg = khadas_mcu_reg_volatile, .writeable_reg = khadas_mcu_reg_writeable, .cache_type = REGCACHE_RBTREE, }; static struct mfd_cell khadas_mcu_fan_cells[] = { /* VIM1/2 Rev13+ and VIM3 only */ { .name = "khadas-mcu-fan-ctrl", }, }; static struct mfd_cell khadas_mcu_cells[] = { { .name = "khadas-mcu-user-mem", }, }; static int khadas_mcu_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; struct khadas_mcu *ddata; int ret; ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); if (!ddata) return -ENOMEM; i2c_set_clientdata(client, ddata); ddata->dev = dev; ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config); if (IS_ERR(ddata->regmap)) { ret = PTR_ERR(ddata->regmap); dev_err(dev, "Failed to allocate register map: %d\n", ret); return ret; } ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, khadas_mcu_cells, ARRAY_SIZE(khadas_mcu_cells), NULL, 0, NULL); if (ret) return ret; if (of_find_property(dev->of_node, "#cooling-cells", NULL)) return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, khadas_mcu_fan_cells, ARRAY_SIZE(khadas_mcu_fan_cells), NULL, 0, NULL); return 0; } #ifdef CONFIG_OF static const struct of_device_id khadas_mcu_of_match[] = { { .compatible = "khadas,mcu", }, {}, }; MODULE_DEVICE_TABLE(of, khadas_mcu_of_match); #endif static struct i2c_driver khadas_mcu_driver = { .driver = { .name = "khadas-mcu-core", .of_match_table = of_match_ptr(khadas_mcu_of_match), }, .probe = khadas_mcu_probe, }; module_i2c_driver(khadas_mcu_driver); MODULE_DESCRIPTION("Khadas MCU core driver"); MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); MODULE_LICENSE("GPL v2");