| // SPDX-License-Identifier: GPL-2.0-only |
| |
| /* |
| * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/i2c.h> |
| #include <linux/slab.h> |
| #include <linux/platform_device.h> |
| #include <linux/input.h> |
| #include <linux/types.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/of.h> |
| #include <linux/of_graph.h> |
| #include <linux/kernel.h> |
| #include <linux/of_gpio.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/rwlock.h> |
| #include <linux/uaccess.h> |
| #include <linux/regmap.h> |
| |
| struct max31760 { |
| struct device *dev; |
| u8 i2c_addr; |
| struct regmap *regmap; |
| u32 fan_pwr_en; |
| u32 fan_pwr_bp; |
| struct i2c_client *i2c_client; |
| int pwm; |
| bool fan_off; |
| }; |
| |
| static void turn_gpio(struct max31760 *pdata, bool on) |
| { |
| if (on) { |
| gpio_direction_output(pdata->fan_pwr_en, 0); |
| gpio_set_value(pdata->fan_pwr_en, 1); |
| pr_debug("%s gpio:%d set to high\n", __func__, |
| pdata->fan_pwr_en); |
| msleep(20); |
| gpio_direction_output(pdata->fan_pwr_bp, 0); |
| gpio_set_value(pdata->fan_pwr_bp, 1); |
| pr_debug("%s gpio:%d set to high\n", __func__, |
| pdata->fan_pwr_bp); |
| msleep(20); |
| } else { |
| gpio_direction_output(pdata->fan_pwr_en, 1); |
| gpio_set_value(pdata->fan_pwr_en, 0); |
| pr_debug("%s gpio:%d set to low\n", __func__, |
| pdata->fan_pwr_en); |
| msleep(20); |
| gpio_direction_output(pdata->fan_pwr_bp, 1); |
| gpio_set_value(pdata->fan_pwr_bp, 0); |
| pr_debug("%s gpio:%d set to low\n", __func__, |
| pdata->fan_pwr_bp); |
| msleep(20); |
| } |
| } |
| |
| static int max31760_i2c_reg_get(struct max31760 *pdata, |
| u8 reg) |
| { |
| int ret; |
| u32 val1; |
| |
| pr_debug("%s, reg:%x\n", __func__, reg); |
| ret = regmap_read(pdata->regmap, (unsigned int)reg, &val1); |
| if (ret < 0) { |
| pr_err("%s failed reading reg 0x%02x failure\n", __func__, reg); |
| return ret; |
| } |
| |
| pr_debug("%s success reading reg 0x%x=0x%x, val1=%x\n", |
| __func__, reg, val1, val1); |
| |
| return 0; |
| } |
| |
| static int max31760_i2c_reg_set(struct max31760 *pdata, |
| u8 reg, u8 val) |
| { |
| int ret; |
| int i; |
| |
| for (i = 0; i < 10; i++) { |
| ret = regmap_write(pdata->regmap, reg, val); |
| if (ret >= 0) |
| return ret; |
| msleep(20); |
| } |
| if (ret < 0) |
| pr_err("%s loop:%d failed to write reg 0x%02x=0x%02x\n", |
| __func__, i, reg, val); |
| return ret; |
| } |
| |
| static ssize_t fan_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct max31760 *pdata; |
| int ret; |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) { |
| pr_err("invalid driver pointer\n"); |
| return -ENODEV; |
| } |
| |
| if (pdata->fan_off) |
| ret = scnprintf(buf, PAGE_SIZE, "off\n"); |
| else |
| ret = scnprintf(buf, PAGE_SIZE, "0x%x\n", pdata->pwm); |
| |
| return ret; |
| } |
| |
| static ssize_t fan_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| long val; |
| struct max31760 *pdata; |
| |
| pdata = dev_get_drvdata(dev); |
| if (!pdata) { |
| pr_err("invalid driver pointer\n"); |
| return -ENODEV; |
| } |
| |
| kstrtol(buf, 0, &val); |
| pr_debug("%s, count:%d val:%lx, buf:%s\n", |
| __func__, count, val, buf); |
| |
| if (val == 0xff) { |
| turn_gpio(pdata, false); |
| pdata->fan_off = true; |
| } else if (val == 0xfe) { |
| pdata->fan_off = false; |
| turn_gpio(pdata, true); |
| max31760_i2c_reg_set(pdata, 0x00, pdata->pwm); |
| } else { |
| max31760_i2c_reg_set(pdata, 0x00, (int)val); |
| pdata->pwm = (int)val; |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR_RW(fan); |
| |
| static struct attribute *max31760_fs_attrs[] = { |
| &dev_attr_fan.attr, |
| NULL |
| }; |
| |
| static struct attribute_group max31760_fs_attr_group = { |
| .attrs = max31760_fs_attrs, |
| }; |
| |
| static int max31760_parse_dt(struct device *dev, |
| struct max31760 *pdata) |
| { |
| struct device_node *np = dev->of_node; |
| int ret; |
| |
| pdata->fan_pwr_en = |
| of_get_named_gpio(np, "qcom,fan-pwr-en", 0); |
| if (!gpio_is_valid(pdata->fan_pwr_en)) { |
| pr_err("%s fan_pwr_en gpio not specified\n", __func__); |
| ret = -EINVAL; |
| } else { |
| ret = gpio_request(pdata->fan_pwr_en, "fan_pwr_en"); |
| if (ret) { |
| pr_err("max31760 fan_pwr_en gpio request failed\n"); |
| goto error1; |
| } |
| } |
| |
| pdata->fan_pwr_bp = |
| of_get_named_gpio(np, "qcom,fan-pwr-bp", 0); |
| if (!gpio_is_valid(pdata->fan_pwr_bp)) { |
| pr_err("%s fan_pwr_bp gpio not specified\n", __func__); |
| ret = -EINVAL; |
| } else |
| ret = gpio_request(pdata->fan_pwr_bp, "fan_pwr_bp"); |
| if (ret) { |
| pr_err("max31760 fan_pwr_bp gpio request failed\n"); |
| goto error2; |
| } |
| turn_gpio(pdata, true); |
| |
| return ret; |
| |
| error2: |
| gpio_free(pdata->fan_pwr_bp); |
| error1: |
| gpio_free(pdata->fan_pwr_en); |
| return ret; |
| } |
| |
| static int max31760_fan_pwr_enable_vregs(struct device *dev, |
| struct max31760 *pdata) |
| { |
| int ret; |
| struct regulator *reg; |
| |
| /* Fan Control LDO L10A */ |
| reg = devm_regulator_get(dev, "pm8150_l10"); |
| if (!IS_ERR(reg)) { |
| regulator_set_load(reg, 600000); |
| ret = regulator_enable(reg); |
| if (ret < 0) { |
| pr_err("%s pm8150_l10 failed\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| /* Fan Control LDO S4 */ |
| reg = devm_regulator_get(dev, "pm8150_s4"); |
| if (!IS_ERR(reg)) { |
| regulator_set_load(reg, 600000); |
| ret = regulator_enable(reg); |
| if (ret < 0) { |
| pr_err("%s pm8150_s4 failed\n", __func__); |
| return -EINVAL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static const struct regmap_config max31760_regmap = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = 0xFF, |
| }; |
| |
| static int max31760_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int ret; |
| struct max31760 *pdata; |
| |
| if (!client || !client->dev.of_node) { |
| pr_err("%s invalid input\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| pr_err("%s device doesn't support I2C\n", __func__); |
| return -ENODEV; |
| } |
| |
| pdata = devm_kzalloc(&client->dev, |
| sizeof(struct max31760), GFP_KERNEL); |
| if (!pdata) |
| return -ENOMEM; |
| |
| pdata->regmap = devm_regmap_init_i2c(client, &max31760_regmap); |
| if (IS_ERR(pdata->regmap)) { |
| ret = PTR_ERR(pdata->regmap); |
| pr_err("%s Failed to allocate regmap: %d\n", __func__, ret); |
| return -EINVAL; |
| } |
| |
| ret = max31760_parse_dt(&client->dev, pdata); |
| if (ret) { |
| pr_err("%s failed to parse device tree\n", __func__); |
| return -EINVAL; |
| } |
| |
| ret = max31760_fan_pwr_enable_vregs(&client->dev, pdata); |
| if (ret) { |
| pr_err("%s failed to pwr regulators\n", __func__); |
| return -EINVAL; |
| } |
| |
| pdata->dev = &client->dev; |
| i2c_set_clientdata(client, pdata); |
| |
| pdata->i2c_client = client; |
| |
| dev_set_drvdata(&client->dev, pdata); |
| |
| ret = sysfs_create_group(&pdata->dev->kobj, &max31760_fs_attr_group); |
| if (ret) |
| pr_err("%s unable to register max31760 sysfs nodes\n"); |
| |
| /* 00 - 0x01 -- 33Hz */ |
| /* 01 - 0x09 -- 150Hz */ |
| /* 10 - 0x11 -- 1500Hz */ |
| /* 11 - 0x19 -- 25Khz */ |
| pdata->pwm = 0x19; |
| max31760_i2c_reg_set(pdata, 0x00, pdata->pwm); |
| max31760_i2c_reg_set(pdata, 0x01, 0x11); |
| max31760_i2c_reg_set(pdata, 0x02, 0x31); |
| max31760_i2c_reg_set(pdata, 0x03, 0x45); |
| max31760_i2c_reg_set(pdata, 0x04, 0xff); |
| max31760_i2c_reg_set(pdata, 0x50, 0xcf); |
| max31760_i2c_reg_set(pdata, 0x01, 0x11); |
| max31760_i2c_reg_set(pdata, 0x00, pdata->pwm); |
| max31760_i2c_reg_get(pdata, 0x00); |
| |
| return ret; |
| } |
| |
| static int max31760_remove(struct i2c_client *client) |
| { |
| struct max31760 *pdata = i2c_get_clientdata(client); |
| |
| if (!pdata) |
| goto end; |
| |
| sysfs_remove_group(&pdata->dev->kobj, &max31760_fs_attr_group); |
| turn_gpio(pdata, false); |
| end: |
| return 0; |
| } |
| |
| |
| static void max31760_shutdown(struct i2c_client *client) |
| { |
| } |
| |
| static int max31760_suspend(struct device *dev, pm_message_t state) |
| { |
| struct max31760 *pdata = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "suspend\n"); |
| if (pdata) |
| turn_gpio(pdata, false); |
| return 0; |
| } |
| |
| static int max31760_resume(struct device *dev) |
| { |
| struct max31760 *pdata = dev_get_drvdata(dev); |
| |
| dev_dbg(dev, "resume\n"); |
| if (pdata) { |
| turn_gpio(pdata, true); |
| max31760_i2c_reg_set(pdata, 0x00, pdata->pwm); |
| } |
| return 0; |
| } |
| |
| static const struct of_device_id max31760_id_table[] = { |
| { .compatible = "maxim,xrfancontroller",}, |
| { }, |
| }; |
| static const struct i2c_device_id max31760_i2c_table[] = { |
| { "xrfancontroller", 0 }, |
| { }, |
| }; |
| |
| static struct i2c_driver max31760_i2c_driver = { |
| .probe = max31760_probe, |
| .remove = max31760_remove, |
| .shutdown = max31760_shutdown, |
| .driver = { |
| .name = "maxim xrfancontroller", |
| .of_match_table = max31760_id_table, |
| .suspend = max31760_suspend, |
| .resume = max31760_resume, |
| }, |
| .id_table = max31760_i2c_table, |
| }; |
| module_i2c_driver(max31760_i2c_driver); |
| MODULE_DEVICE_TABLE(i2c, max31760_i2c_table); |
| MODULE_DESCRIPTION("Maxim 31760 Fan Controller"); |
| MODULE_LICENSE("GPL v2"); |