1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2023 IBM Corp. |
4 | */ |
5 | |
6 | #include <linux/debugfs.h> |
7 | #include <linux/device.h> |
8 | #include <linux/fs.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/minmax.h> |
11 | #include <linux/module.h> |
12 | #include <linux/pmbus.h> |
13 | #include <linux/hwmon-sysfs.h> |
14 | #include "pmbus.h" |
15 | |
16 | #define ACBEL_MFR_FW_REVISION 0xd9 |
17 | |
18 | static ssize_t acbel_fsg032_debugfs_read(struct file *file, char __user *buf, size_t count, |
19 | loff_t *ppos) |
20 | { |
21 | struct i2c_client *client = file->private_data; |
22 | u8 data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; |
23 | char out[8]; |
24 | int rc; |
25 | |
26 | rc = i2c_smbus_read_block_data(client, ACBEL_MFR_FW_REVISION, values: data); |
27 | if (rc < 0) |
28 | return rc; |
29 | |
30 | rc = snprintf(buf: out, size: sizeof(out), fmt: "%*phN\n" , min(rc, 3), data); |
31 | return simple_read_from_buffer(to: buf, count, ppos, from: out, available: rc); |
32 | } |
33 | |
34 | static const struct file_operations acbel_debugfs_ops = { |
35 | .llseek = noop_llseek, |
36 | .read = acbel_fsg032_debugfs_read, |
37 | .write = NULL, |
38 | .open = simple_open, |
39 | }; |
40 | |
41 | static void acbel_fsg032_init_debugfs(struct i2c_client *client) |
42 | { |
43 | struct dentry *debugfs = pmbus_get_debugfs_dir(client); |
44 | |
45 | if (!debugfs) |
46 | return; |
47 | |
48 | debugfs_create_file(name: "fw_version" , mode: 0444, parent: debugfs, data: client, fops: &acbel_debugfs_ops); |
49 | } |
50 | |
51 | static const struct i2c_device_id acbel_fsg032_id[] = { |
52 | { "acbel_fsg032" }, |
53 | {} |
54 | }; |
55 | |
56 | static struct pmbus_driver_info acbel_fsg032_info = { |
57 | .pages = 1, |
58 | .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | |
59 | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | |
60 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | |
61 | PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_VOUT | |
62 | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP | |
63 | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_FAN12, |
64 | }; |
65 | |
66 | static int acbel_fsg032_probe(struct i2c_client *client) |
67 | { |
68 | u8 buf[I2C_SMBUS_BLOCK_MAX + 1]; |
69 | struct device *dev = &client->dev; |
70 | int rc; |
71 | |
72 | rc = i2c_smbus_read_block_data(client, command: PMBUS_MFR_ID, values: buf); |
73 | if (rc < 0) { |
74 | dev_err(dev, "Failed to read PMBUS_MFR_ID\n" ); |
75 | return rc; |
76 | } |
77 | if (strncmp(buf, "ACBEL" , 5)) { |
78 | buf[rc] = '\0'; |
79 | dev_err(dev, "Manufacturer '%s' not supported\n" , buf); |
80 | return -ENODEV; |
81 | } |
82 | |
83 | rc = i2c_smbus_read_block_data(client, command: PMBUS_MFR_MODEL, values: buf); |
84 | if (rc < 0) { |
85 | dev_err(dev, "Failed to read PMBUS_MFR_MODEL\n" ); |
86 | return rc; |
87 | } |
88 | |
89 | if (strncmp(buf, "FSG032" , 6)) { |
90 | buf[rc] = '\0'; |
91 | dev_err(dev, "Model '%s' not supported\n" , buf); |
92 | return -ENODEV; |
93 | } |
94 | |
95 | rc = pmbus_do_probe(client, info: &acbel_fsg032_info); |
96 | if (rc) |
97 | return rc; |
98 | |
99 | acbel_fsg032_init_debugfs(client); |
100 | return 0; |
101 | } |
102 | |
103 | static const struct of_device_id acbel_fsg032_of_match[] = { |
104 | { .compatible = "acbel,fsg032" }, |
105 | {} |
106 | }; |
107 | MODULE_DEVICE_TABLE(of, acbel_fsg032_of_match); |
108 | |
109 | static struct i2c_driver acbel_fsg032_driver = { |
110 | .driver = { |
111 | .name = "acbel-fsg032" , |
112 | .of_match_table = acbel_fsg032_of_match, |
113 | }, |
114 | .probe = acbel_fsg032_probe, |
115 | .id_table = acbel_fsg032_id, |
116 | }; |
117 | |
118 | module_i2c_driver(acbel_fsg032_driver); |
119 | |
120 | MODULE_AUTHOR("Lakshmi Yadlapati" ); |
121 | MODULE_DESCRIPTION("PMBus driver for AcBel Power System power supplies" ); |
122 | MODULE_LICENSE("GPL" ); |
123 | MODULE_IMPORT_NS(PMBUS); |
124 | |