brintos

brintos / linux-shallow public Read only

0
0
Text · 15.0 KiB · 02f5d35 Raw
601 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * A hwmon driver for the Intel 5000 series chipset FB-DIMM AMB4 * temperature sensors5 * Copyright (C) 2007 IBM6 *7 * Author: Darrick J. Wong <darrick.wong@oracle.com>8 */9 10#include <linux/module.h>11#include <linux/hwmon.h>12#include <linux/hwmon-sysfs.h>13#include <linux/err.h>14#include <linux/mutex.h>15#include <linux/log2.h>16#include <linux/pci.h>17#include <linux/platform_device.h>18#include <linux/slab.h>19 20#define DRVNAME "i5k_amb"21 22#define I5K_REG_AMB_BASE_ADDR		0x4823#define I5K_REG_AMB_LEN_ADDR		0x5024#define I5K_REG_CHAN0_PRESENCE_ADDR	0x6425#define I5K_REG_CHAN1_PRESENCE_ADDR	0x6626 27#define AMB_REG_TEMP_MIN_ADDR		0x8028#define AMB_REG_TEMP_MID_ADDR		0x8129#define AMB_REG_TEMP_MAX_ADDR		0x8230#define AMB_REG_TEMP_STATUS_ADDR	0x8431#define AMB_REG_TEMP_ADDR		0x8532 33#define AMB_CONFIG_SIZE			204834#define AMB_FUNC_3_OFFSET		76835 36static unsigned long amb_reg_temp_status(unsigned int amb)37{38	return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_STATUS_ADDR +39	       AMB_CONFIG_SIZE * amb;40}41 42static unsigned long amb_reg_temp_min(unsigned int amb)43{44	return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MIN_ADDR +45	       AMB_CONFIG_SIZE * amb;46}47 48static unsigned long amb_reg_temp_mid(unsigned int amb)49{50	return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MID_ADDR +51	       AMB_CONFIG_SIZE * amb;52}53 54static unsigned long amb_reg_temp_max(unsigned int amb)55{56	return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MAX_ADDR +57	       AMB_CONFIG_SIZE * amb;58}59 60static unsigned long amb_reg_temp(unsigned int amb)61{62	return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_ADDR +63	       AMB_CONFIG_SIZE * amb;64}65 66#define MAX_MEM_CHANNELS		467#define MAX_AMBS_PER_CHANNEL		1668#define MAX_AMBS			(MAX_MEM_CHANNELS * \69					 MAX_AMBS_PER_CHANNEL)70#define CHANNEL_SHIFT			471#define DIMM_MASK			0xF72/*73 * Ugly hack: For some reason the highest bit is set if there74 * are _any_ DIMMs in the channel.  Attempting to read from75 * this "high-order" AMB results in a memory bus error, so76 * for now we'll just ignore that top bit, even though that77 * might prevent us from seeing the 16th DIMM in the channel.78 */79#define REAL_MAX_AMBS_PER_CHANNEL	1580#define KNOBS_PER_AMB			681 82static unsigned long amb_num_from_reg(unsigned int byte_num, unsigned int bit)83{84	return byte_num * MAX_AMBS_PER_CHANNEL + bit;85}86 87#define AMB_SYSFS_NAME_LEN		1688struct i5k_device_attribute {89	struct sensor_device_attribute s_attr;90	char name[AMB_SYSFS_NAME_LEN];91};92 93struct i5k_amb_data {94	struct device *hwmon_dev;95 96	unsigned long amb_base;97	unsigned long amb_len;98	u16 amb_present[MAX_MEM_CHANNELS];99	void __iomem *amb_mmio;100	struct i5k_device_attribute *attrs;101	unsigned int num_attrs;102};103 104static DEVICE_STRING_ATTR_RO(name, 0444, DRVNAME);105 106static struct platform_device *amb_pdev;107 108static u8 amb_read_byte(struct i5k_amb_data *data, unsigned long offset)109{110	return ioread8(data->amb_mmio + offset);111}112 113static void amb_write_byte(struct i5k_amb_data *data, unsigned long offset,114			   u8 val)115{116	iowrite8(val, data->amb_mmio + offset);117}118 119static ssize_t show_amb_alarm(struct device *dev,120			     struct device_attribute *devattr,121			     char *buf)122{123	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);124	struct i5k_amb_data *data = dev_get_drvdata(dev);125 126	if (!(amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x20) &&127	     (amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x8))128		return sprintf(buf, "1\n");129	else130		return sprintf(buf, "0\n");131}132 133static ssize_t store_amb_min(struct device *dev,134			     struct device_attribute *devattr,135			     const char *buf,136			     size_t count)137{138	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);139	struct i5k_amb_data *data = dev_get_drvdata(dev);140	unsigned long temp;141	int ret = kstrtoul(buf, 10, &temp);142	if (ret < 0)143		return ret;144 145	temp = temp / 500;146	if (temp > 255)147		temp = 255;148 149	amb_write_byte(data, amb_reg_temp_min(attr->index), temp);150	return count;151}152 153static ssize_t store_amb_mid(struct device *dev,154			     struct device_attribute *devattr,155			     const char *buf,156			     size_t count)157{158	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);159	struct i5k_amb_data *data = dev_get_drvdata(dev);160	unsigned long temp;161	int ret = kstrtoul(buf, 10, &temp);162	if (ret < 0)163		return ret;164 165	temp = temp / 500;166	if (temp > 255)167		temp = 255;168 169	amb_write_byte(data, amb_reg_temp_mid(attr->index), temp);170	return count;171}172 173static ssize_t store_amb_max(struct device *dev,174			     struct device_attribute *devattr,175			     const char *buf,176			     size_t count)177{178	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);179	struct i5k_amb_data *data = dev_get_drvdata(dev);180	unsigned long temp;181	int ret = kstrtoul(buf, 10, &temp);182	if (ret < 0)183		return ret;184 185	temp = temp / 500;186	if (temp > 255)187		temp = 255;188 189	amb_write_byte(data, amb_reg_temp_max(attr->index), temp);190	return count;191}192 193static ssize_t show_amb_min(struct device *dev,194			     struct device_attribute *devattr,195			     char *buf)196{197	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);198	struct i5k_amb_data *data = dev_get_drvdata(dev);199	return sprintf(buf, "%d\n",200		500 * amb_read_byte(data, amb_reg_temp_min(attr->index)));201}202 203static ssize_t show_amb_mid(struct device *dev,204			     struct device_attribute *devattr,205			     char *buf)206{207	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);208	struct i5k_amb_data *data = dev_get_drvdata(dev);209	return sprintf(buf, "%d\n",210		500 * amb_read_byte(data, amb_reg_temp_mid(attr->index)));211}212 213static ssize_t show_amb_max(struct device *dev,214			     struct device_attribute *devattr,215			     char *buf)216{217	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);218	struct i5k_amb_data *data = dev_get_drvdata(dev);219	return sprintf(buf, "%d\n",220		500 * amb_read_byte(data, amb_reg_temp_max(attr->index)));221}222 223static ssize_t show_amb_temp(struct device *dev,224			     struct device_attribute *devattr,225			     char *buf)226{227	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);228	struct i5k_amb_data *data = dev_get_drvdata(dev);229	return sprintf(buf, "%d\n",230		500 * amb_read_byte(data, amb_reg_temp(attr->index)));231}232 233static ssize_t show_label(struct device *dev,234			  struct device_attribute *devattr,235			  char *buf)236{237	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);238 239	return sprintf(buf, "Ch. %d DIMM %d\n", attr->index >> CHANNEL_SHIFT,240		       attr->index & DIMM_MASK);241}242 243static int i5k_amb_hwmon_init(struct platform_device *pdev)244{245	int i, j, k, d = 0;246	u16 c;247	int res = 0;248	int num_ambs = 0;249	struct i5k_amb_data *data = platform_get_drvdata(pdev);250 251	/* Count the number of AMBs found */252	/* ignore the high-order bit, see "Ugly hack" comment above */253	for (i = 0; i < MAX_MEM_CHANNELS; i++)254		num_ambs += hweight16(data->amb_present[i] & 0x7fff);255 256	/* Set up sysfs stuff */257	data->attrs = kzalloc(array3_size(num_ambs, KNOBS_PER_AMB,258					  sizeof(*data->attrs)),259			      GFP_KERNEL);260	if (!data->attrs)261		return -ENOMEM;262	data->num_attrs = 0;263 264	for (i = 0; i < MAX_MEM_CHANNELS; i++) {265		c = data->amb_present[i];266		for (j = 0; j < REAL_MAX_AMBS_PER_CHANNEL; j++, c >>= 1) {267			struct i5k_device_attribute *iattr;268 269			k = amb_num_from_reg(i, j);270			if (!(c & 0x1))271				continue;272			d++;273 274			/* sysfs label */275			iattr = data->attrs + data->num_attrs;276			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,277				 "temp%d_label", d);278			iattr->s_attr.dev_attr.attr.name = iattr->name;279			iattr->s_attr.dev_attr.attr.mode = 0444;280			iattr->s_attr.dev_attr.show = show_label;281			iattr->s_attr.index = k;282			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);283			res = device_create_file(&pdev->dev,284						 &iattr->s_attr.dev_attr);285			if (res)286				goto exit_remove;287			data->num_attrs++;288 289			/* Temperature sysfs knob */290			iattr = data->attrs + data->num_attrs;291			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,292				 "temp%d_input", d);293			iattr->s_attr.dev_attr.attr.name = iattr->name;294			iattr->s_attr.dev_attr.attr.mode = 0444;295			iattr->s_attr.dev_attr.show = show_amb_temp;296			iattr->s_attr.index = k;297			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);298			res = device_create_file(&pdev->dev,299						 &iattr->s_attr.dev_attr);300			if (res)301				goto exit_remove;302			data->num_attrs++;303 304			/* Temperature min sysfs knob */305			iattr = data->attrs + data->num_attrs;306			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,307				 "temp%d_min", d);308			iattr->s_attr.dev_attr.attr.name = iattr->name;309			iattr->s_attr.dev_attr.attr.mode = 0644;310			iattr->s_attr.dev_attr.show = show_amb_min;311			iattr->s_attr.dev_attr.store = store_amb_min;312			iattr->s_attr.index = k;313			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);314			res = device_create_file(&pdev->dev,315						 &iattr->s_attr.dev_attr);316			if (res)317				goto exit_remove;318			data->num_attrs++;319 320			/* Temperature mid sysfs knob */321			iattr = data->attrs + data->num_attrs;322			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,323				 "temp%d_mid", d);324			iattr->s_attr.dev_attr.attr.name = iattr->name;325			iattr->s_attr.dev_attr.attr.mode = 0644;326			iattr->s_attr.dev_attr.show = show_amb_mid;327			iattr->s_attr.dev_attr.store = store_amb_mid;328			iattr->s_attr.index = k;329			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);330			res = device_create_file(&pdev->dev,331						 &iattr->s_attr.dev_attr);332			if (res)333				goto exit_remove;334			data->num_attrs++;335 336			/* Temperature max sysfs knob */337			iattr = data->attrs + data->num_attrs;338			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,339				 "temp%d_max", d);340			iattr->s_attr.dev_attr.attr.name = iattr->name;341			iattr->s_attr.dev_attr.attr.mode = 0644;342			iattr->s_attr.dev_attr.show = show_amb_max;343			iattr->s_attr.dev_attr.store = store_amb_max;344			iattr->s_attr.index = k;345			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);346			res = device_create_file(&pdev->dev,347						 &iattr->s_attr.dev_attr);348			if (res)349				goto exit_remove;350			data->num_attrs++;351 352			/* Temperature alarm sysfs knob */353			iattr = data->attrs + data->num_attrs;354			snprintf(iattr->name, AMB_SYSFS_NAME_LEN,355				 "temp%d_alarm", d);356			iattr->s_attr.dev_attr.attr.name = iattr->name;357			iattr->s_attr.dev_attr.attr.mode = 0444;358			iattr->s_attr.dev_attr.show = show_amb_alarm;359			iattr->s_attr.index = k;360			sysfs_attr_init(&iattr->s_attr.dev_attr.attr);361			res = device_create_file(&pdev->dev,362						 &iattr->s_attr.dev_attr);363			if (res)364				goto exit_remove;365			data->num_attrs++;366		}367	}368 369	res = device_create_file(&pdev->dev, &dev_attr_name.attr);370	if (res)371		goto exit_remove;372 373	data->hwmon_dev = hwmon_device_register(&pdev->dev);374	if (IS_ERR(data->hwmon_dev)) {375		res = PTR_ERR(data->hwmon_dev);376		goto exit_remove;377	}378 379	return res;380 381exit_remove:382	device_remove_file(&pdev->dev, &dev_attr_name.attr);383	for (i = 0; i < data->num_attrs; i++)384		device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);385	kfree(data->attrs);386 387	return res;388}389 390static int i5k_amb_add(void)391{392	int res;393 394	/* only ever going to be one of these */395	amb_pdev = platform_device_alloc(DRVNAME, 0);396	if (!amb_pdev)397		return -ENOMEM;398 399	res = platform_device_add(amb_pdev);400	if (res)401		goto err;402	return 0;403 404err:405	platform_device_put(amb_pdev);406	return res;407}408 409static int i5k_find_amb_registers(struct i5k_amb_data *data,410					    unsigned long devid)411{412	struct pci_dev *pcidev;413	u32 val32;414	int res = -ENODEV;415 416	/* Find AMB register memory space */417	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,418				devid,419				NULL);420	if (!pcidev)421		return -ENODEV;422 423	pci_read_config_dword(pcidev, I5K_REG_AMB_BASE_ADDR, &val32);424	if (val32 == (u32)~0)425		goto out;426	data->amb_base = val32;427 428	pci_read_config_dword(pcidev, I5K_REG_AMB_LEN_ADDR, &val32);429	if (val32 == (u32)~0)430		goto out;431	data->amb_len = val32;432 433	/* Is it big enough? */434	if (data->amb_len < AMB_CONFIG_SIZE * MAX_AMBS) {435		dev_err(&pcidev->dev, "AMB region too small!\n");436		goto out;437	}438 439	res = 0;440out:441	pci_dev_put(pcidev);442	return res;443}444 445static int i5k_channel_probe(u16 *amb_present, unsigned long dev_id)446{447	struct pci_dev *pcidev;448	u16 val16;449	int res = -ENODEV;450 451	/* Copy the DIMM presence map for these two channels */452	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, dev_id, NULL);453	if (!pcidev)454		return -ENODEV;455 456	pci_read_config_word(pcidev, I5K_REG_CHAN0_PRESENCE_ADDR, &val16);457	if (val16 == (u16)~0)458		goto out;459	amb_present[0] = val16;460 461	pci_read_config_word(pcidev, I5K_REG_CHAN1_PRESENCE_ADDR, &val16);462	if (val16 == (u16)~0)463		goto out;464	amb_present[1] = val16;465 466	res = 0;467 468out:469	pci_dev_put(pcidev);470	return res;471}472 473static struct {474	unsigned long err;475	unsigned long fbd0;476} chipset_ids[]  = {477	{ PCI_DEVICE_ID_INTEL_5000_ERR, PCI_DEVICE_ID_INTEL_5000_FBD0 },478	{ PCI_DEVICE_ID_INTEL_5400_ERR, PCI_DEVICE_ID_INTEL_5400_FBD0 },479	{ 0, 0 }480};481 482#ifdef MODULE483static const struct pci_device_id i5k_amb_ids[] = {484	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) },485	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) },486	{ 0, }487};488MODULE_DEVICE_TABLE(pci, i5k_amb_ids);489#endif490 491static int i5k_amb_probe(struct platform_device *pdev)492{493	struct i5k_amb_data *data;494	struct resource *reso;495	int i, res;496 497	data = kzalloc(sizeof(*data), GFP_KERNEL);498	if (!data)499		return -ENOMEM;500 501	/* Figure out where the AMB registers live */502	i = 0;503	do {504		res = i5k_find_amb_registers(data, chipset_ids[i].err);505		if (res == 0)506			break;507		i++;508	} while (chipset_ids[i].err);509 510	if (res)511		goto err;512 513	/* Copy the DIMM presence map for the first two channels */514	res = i5k_channel_probe(&data->amb_present[0], chipset_ids[i].fbd0);515	if (res)516		goto err;517 518	/* Copy the DIMM presence map for the optional second two channels */519	i5k_channel_probe(&data->amb_present[2], chipset_ids[i].fbd0 + 1);520 521	/* Set up resource regions */522	reso = request_mem_region(data->amb_base, data->amb_len, DRVNAME);523	if (!reso) {524		res = -EBUSY;525		goto err;526	}527 528	data->amb_mmio = ioremap(data->amb_base, data->amb_len);529	if (!data->amb_mmio) {530		res = -EBUSY;531		goto err_map_failed;532	}533 534	platform_set_drvdata(pdev, data);535 536	res = i5k_amb_hwmon_init(pdev);537	if (res)538		goto err_init_failed;539 540	return res;541 542err_init_failed:543	iounmap(data->amb_mmio);544err_map_failed:545	release_mem_region(data->amb_base, data->amb_len);546err:547	kfree(data);548	return res;549}550 551static void i5k_amb_remove(struct platform_device *pdev)552{553	int i;554	struct i5k_amb_data *data = platform_get_drvdata(pdev);555 556	hwmon_device_unregister(data->hwmon_dev);557	device_remove_file(&pdev->dev, &dev_attr_name.attr);558	for (i = 0; i < data->num_attrs; i++)559		device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);560	kfree(data->attrs);561	iounmap(data->amb_mmio);562	release_mem_region(data->amb_base, data->amb_len);563	kfree(data);564}565 566static struct platform_driver i5k_amb_driver = {567	.driver = {568		.name = DRVNAME,569	},570	.probe = i5k_amb_probe,571	.remove_new = i5k_amb_remove,572};573 574static int __init i5k_amb_init(void)575{576	int res;577 578	res = platform_driver_register(&i5k_amb_driver);579	if (res)580		return res;581 582	res = i5k_amb_add();583	if (res)584		platform_driver_unregister(&i5k_amb_driver);585 586	return res;587}588 589static void __exit i5k_amb_exit(void)590{591	platform_device_unregister(amb_pdev);592	platform_driver_unregister(&i5k_amb_driver);593}594 595MODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>");596MODULE_DESCRIPTION("Intel 5000 chipset FB-DIMM AMB temperature sensor");597MODULE_LICENSE("GPL");598 599module_init(i5k_amb_init);600module_exit(i5k_amb_exit);601