18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  MEN 14F021P00 Board Management Controller (BMC) MFD Core Driver.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/device.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/i2c.h>
128c2ecf20Sopenharmony_ci#include <linux/mfd/core.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#define BMC_CMD_WDT_EXIT_PROD	0x18
158c2ecf20Sopenharmony_ci#define BMC_CMD_WDT_PROD_STAT	0x19
168c2ecf20Sopenharmony_ci#define BMC_CMD_REV_MAJOR	0x80
178c2ecf20Sopenharmony_ci#define BMC_CMD_REV_MINOR	0x81
188c2ecf20Sopenharmony_ci#define BMC_CMD_REV_MAIN	0x82
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic struct mfd_cell menf21bmc_cell[] = {
218c2ecf20Sopenharmony_ci	{ .name = "menf21bmc_wdt", },
228c2ecf20Sopenharmony_ci	{ .name = "menf21bmc_led", },
238c2ecf20Sopenharmony_ci	{ .name = "menf21bmc_hwmon", }
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic int menf21bmc_wdt_exit_prod_mode(struct i2c_client *client)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	int val, ret;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	val = i2c_smbus_read_byte_data(client, BMC_CMD_WDT_PROD_STAT);
318c2ecf20Sopenharmony_ci	if (val < 0)
328c2ecf20Sopenharmony_ci		return val;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	/*
358c2ecf20Sopenharmony_ci	 * Production mode should be not active after delivery of the Board.
368c2ecf20Sopenharmony_ci	 * To be sure we check it, inform the user and exit the mode
378c2ecf20Sopenharmony_ci	 * if active.
388c2ecf20Sopenharmony_ci	 */
398c2ecf20Sopenharmony_ci	if (val == 0x00) {
408c2ecf20Sopenharmony_ci		dev_info(&client->dev,
418c2ecf20Sopenharmony_ci			"BMC in production mode. Exit production mode\n");
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci		ret = i2c_smbus_write_byte(client, BMC_CMD_WDT_EXIT_PROD);
448c2ecf20Sopenharmony_ci		if (ret < 0)
458c2ecf20Sopenharmony_ci			return ret;
468c2ecf20Sopenharmony_ci	}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	return 0;
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int
528c2ecf20Sopenharmony_cimenf21bmc_probe(struct i2c_client *client, const struct i2c_device_id *ids)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	int rev_major, rev_minor, rev_main;
558c2ecf20Sopenharmony_ci	int ret;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	ret = i2c_check_functionality(client->adapter,
588c2ecf20Sopenharmony_ci				      I2C_FUNC_SMBUS_BYTE_DATA |
598c2ecf20Sopenharmony_ci				      I2C_FUNC_SMBUS_WORD_DATA |
608c2ecf20Sopenharmony_ci				      I2C_FUNC_SMBUS_BYTE);
618c2ecf20Sopenharmony_ci	if (!ret)
628c2ecf20Sopenharmony_ci		return -ENODEV;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	rev_major = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAJOR);
658c2ecf20Sopenharmony_ci	if (rev_major < 0) {
668c2ecf20Sopenharmony_ci		dev_err(&client->dev, "failed to get BMC major revision\n");
678c2ecf20Sopenharmony_ci		return rev_major;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	rev_minor = i2c_smbus_read_word_data(client, BMC_CMD_REV_MINOR);
718c2ecf20Sopenharmony_ci	if (rev_minor < 0) {
728c2ecf20Sopenharmony_ci		dev_err(&client->dev, "failed to get BMC minor revision\n");
738c2ecf20Sopenharmony_ci		return rev_minor;
748c2ecf20Sopenharmony_ci	}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	rev_main = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAIN);
778c2ecf20Sopenharmony_ci	if (rev_main < 0) {
788c2ecf20Sopenharmony_ci		dev_err(&client->dev, "failed to get BMC main revision\n");
798c2ecf20Sopenharmony_ci		return rev_main;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	dev_info(&client->dev, "FW Revision: %02d.%02d.%02d\n",
838c2ecf20Sopenharmony_ci		 rev_major, rev_minor, rev_main);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	/*
868c2ecf20Sopenharmony_ci	 * We have to exit the Production Mode of the BMC to activate the
878c2ecf20Sopenharmony_ci	 * Watchdog functionality and the BIOS life sign monitoring.
888c2ecf20Sopenharmony_ci	 */
898c2ecf20Sopenharmony_ci	ret = menf21bmc_wdt_exit_prod_mode(client);
908c2ecf20Sopenharmony_ci	if (ret < 0) {
918c2ecf20Sopenharmony_ci		dev_err(&client->dev, "failed to leave production mode\n");
928c2ecf20Sopenharmony_ci		return ret;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	ret = devm_mfd_add_devices(&client->dev, 0, menf21bmc_cell,
968c2ecf20Sopenharmony_ci				   ARRAY_SIZE(menf21bmc_cell), NULL, 0, NULL);
978c2ecf20Sopenharmony_ci	if (ret < 0) {
988c2ecf20Sopenharmony_ci		dev_err(&client->dev, "failed to add BMC sub-devices\n");
998c2ecf20Sopenharmony_ci		return ret;
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	return 0;
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic const struct i2c_device_id menf21bmc_id_table[] = {
1068c2ecf20Sopenharmony_ci	{ "menf21bmc" },
1078c2ecf20Sopenharmony_ci	{ }
1088c2ecf20Sopenharmony_ci};
1098c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, menf21bmc_id_table);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic struct i2c_driver menf21bmc_driver = {
1128c2ecf20Sopenharmony_ci	.driver.name	= "menf21bmc",
1138c2ecf20Sopenharmony_ci	.id_table	= menf21bmc_id_table,
1148c2ecf20Sopenharmony_ci	.probe		= menf21bmc_probe,
1158c2ecf20Sopenharmony_ci};
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cimodule_i2c_driver(menf21bmc_driver);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MEN 14F021P00 BMC mfd core driver");
1208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
1218c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
122