162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Power-source driver for Bay Trail Crystal Cove PMIC 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Based on intel_crystalcove_pwrsrc.c from Android kernel sources, which is: 862306a36Sopenharmony_ci * Copyright (C) 2013 Intel Corporation 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/debugfs.h> 1262306a36Sopenharmony_ci#include <linux/mfd/intel_soc_pmic.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define CRYSTALCOVE_SPWRSRC_REG 0x1E 1862306a36Sopenharmony_ci#define CRYSTALCOVE_RESETSRC0_REG 0x20 1962306a36Sopenharmony_ci#define CRYSTALCOVE_RESETSRC1_REG 0x21 2062306a36Sopenharmony_ci#define CRYSTALCOVE_WAKESRC_REG 0x22 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct crc_pwrsrc_data { 2362306a36Sopenharmony_ci struct regmap *regmap; 2462306a36Sopenharmony_ci struct dentry *debug_dentry; 2562306a36Sopenharmony_ci unsigned int resetsrc0; 2662306a36Sopenharmony_ci unsigned int resetsrc1; 2762306a36Sopenharmony_ci unsigned int wakesrc; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic const char * const pwrsrc_pwrsrc_info[] = { 3162306a36Sopenharmony_ci /* bit 0 */ "USB", 3262306a36Sopenharmony_ci /* bit 1 */ "DC in", 3362306a36Sopenharmony_ci /* bit 2 */ "Battery", 3462306a36Sopenharmony_ci NULL, 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic const char * const pwrsrc_resetsrc0_info[] = { 3862306a36Sopenharmony_ci /* bit 0 */ "SOC reporting a thermal event", 3962306a36Sopenharmony_ci /* bit 1 */ "critical PMIC temperature", 4062306a36Sopenharmony_ci /* bit 2 */ "critical system temperature", 4162306a36Sopenharmony_ci /* bit 3 */ "critical battery temperature", 4262306a36Sopenharmony_ci /* bit 4 */ "VSYS under voltage", 4362306a36Sopenharmony_ci /* bit 5 */ "VSYS over voltage", 4462306a36Sopenharmony_ci /* bit 6 */ "battery removal", 4562306a36Sopenharmony_ci NULL, 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic const char * const pwrsrc_resetsrc1_info[] = { 4962306a36Sopenharmony_ci /* bit 0 */ "VCRIT threshold", 5062306a36Sopenharmony_ci /* bit 1 */ "BATID reporting battery removal", 5162306a36Sopenharmony_ci /* bit 2 */ "user pressing the power button", 5262306a36Sopenharmony_ci NULL, 5362306a36Sopenharmony_ci}; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic const char * const pwrsrc_wakesrc_info[] = { 5662306a36Sopenharmony_ci /* bit 0 */ "user pressing the power button", 5762306a36Sopenharmony_ci /* bit 1 */ "a battery insertion", 5862306a36Sopenharmony_ci /* bit 2 */ "a USB charger insertion", 5962306a36Sopenharmony_ci /* bit 3 */ "an adapter insertion", 6062306a36Sopenharmony_ci NULL, 6162306a36Sopenharmony_ci}; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic void crc_pwrsrc_log(struct seq_file *seq, const char *prefix, 6462306a36Sopenharmony_ci const char * const *info, unsigned int reg_val) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci int i; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci for (i = 0; info[i]; i++) { 6962306a36Sopenharmony_ci if (reg_val & BIT(i)) 7062306a36Sopenharmony_ci seq_printf(seq, "%s by %s\n", prefix, info[i]); 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int pwrsrc_show(struct seq_file *seq, void *unused) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct crc_pwrsrc_data *data = seq->private; 7762306a36Sopenharmony_ci unsigned int reg_val; 7862306a36Sopenharmony_ci int ret; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci ret = regmap_read(data->regmap, CRYSTALCOVE_SPWRSRC_REG, ®_val); 8162306a36Sopenharmony_ci if (ret) 8262306a36Sopenharmony_ci return ret; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci crc_pwrsrc_log(seq, "System powered", pwrsrc_pwrsrc_info, reg_val); 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int resetsrc_show(struct seq_file *seq, void *unused) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct crc_pwrsrc_data *data = seq->private; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci crc_pwrsrc_log(seq, "Last shutdown caused", pwrsrc_resetsrc0_info, data->resetsrc0); 9362306a36Sopenharmony_ci crc_pwrsrc_log(seq, "Last shutdown caused", pwrsrc_resetsrc1_info, data->resetsrc1); 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic int wakesrc_show(struct seq_file *seq, void *unused) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct crc_pwrsrc_data *data = seq->private; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci crc_pwrsrc_log(seq, "Last wake caused", pwrsrc_wakesrc_info, data->wakesrc); 10262306a36Sopenharmony_ci return 0; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(pwrsrc); 10662306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(resetsrc); 10762306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(wakesrc); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int crc_pwrsrc_read_and_clear(struct crc_pwrsrc_data *data, 11062306a36Sopenharmony_ci unsigned int reg, unsigned int *val) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci int ret; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci ret = regmap_read(data->regmap, reg, val); 11562306a36Sopenharmony_ci if (ret) 11662306a36Sopenharmony_ci return ret; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return regmap_write(data->regmap, reg, *val); 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_cistatic int crc_pwrsrc_probe(struct platform_device *pdev) 12262306a36Sopenharmony_ci{ 12362306a36Sopenharmony_ci struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); 12462306a36Sopenharmony_ci struct crc_pwrsrc_data *data; 12562306a36Sopenharmony_ci int ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 12862306a36Sopenharmony_ci if (!data) 12962306a36Sopenharmony_ci return -ENOMEM; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci data->regmap = pmic->regmap; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* 13462306a36Sopenharmony_ci * Read + clear resetsrc0/1 and wakesrc now, so that they get 13562306a36Sopenharmony_ci * cleared even if the debugfs interface is never used. 13662306a36Sopenharmony_ci * 13762306a36Sopenharmony_ci * Properly clearing the wakesrc is important, leaving bit 0 of it 13862306a36Sopenharmony_ci * set turns reboot into poweroff on some tablets. 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_ci ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC0_REG, &data->resetsrc0); 14162306a36Sopenharmony_ci if (ret) 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_RESETSRC1_REG, &data->resetsrc1); 14562306a36Sopenharmony_ci if (ret) 14662306a36Sopenharmony_ci return ret; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ret = crc_pwrsrc_read_and_clear(data, CRYSTALCOVE_WAKESRC_REG, &data->wakesrc); 14962306a36Sopenharmony_ci if (ret) 15062306a36Sopenharmony_ci return ret; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL); 15362306a36Sopenharmony_ci debugfs_create_file("pwrsrc", 0444, data->debug_dentry, data, &pwrsrc_fops); 15462306a36Sopenharmony_ci debugfs_create_file("resetsrc", 0444, data->debug_dentry, data, &resetsrc_fops); 15562306a36Sopenharmony_ci debugfs_create_file("wakesrc", 0444, data->debug_dentry, data, &wakesrc_fops); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci platform_set_drvdata(pdev, data); 15862306a36Sopenharmony_ci return 0; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int crc_pwrsrc_remove(struct platform_device *pdev) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct crc_pwrsrc_data *data = platform_get_drvdata(pdev); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci debugfs_remove_recursive(data->debug_dentry); 16662306a36Sopenharmony_ci return 0; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic struct platform_driver crc_pwrsrc_driver = { 17062306a36Sopenharmony_ci .probe = crc_pwrsrc_probe, 17162306a36Sopenharmony_ci .remove = crc_pwrsrc_remove, 17262306a36Sopenharmony_ci .driver = { 17362306a36Sopenharmony_ci .name = "crystal_cove_pwrsrc", 17462306a36Sopenharmony_ci }, 17562306a36Sopenharmony_ci}; 17662306a36Sopenharmony_cimodule_platform_driver(crc_pwrsrc_driver); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ciMODULE_ALIAS("platform:crystal_cove_pwrsrc"); 17962306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 18062306a36Sopenharmony_ciMODULE_DESCRIPTION("Power-source driver for Bay Trail Crystal Cove PMIC"); 18162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 182