Blame view

drivers/hwmon/k8temp.c 5.42 KB
162163332   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
29fa06c12   Rudolf Marek   hwmon: New driver...
2
3
4
  /*
   * k8temp.c - Linux kernel module for hardware monitoring
   *
7188cc66b   Jean Delvare   hwmon: Update Rud...
5
   * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
29fa06c12   Rudolf Marek   hwmon: New driver...
6
7
   *
   * Inspired from the w83785 and amd756 drivers.
29fa06c12   Rudolf Marek   hwmon: New driver...
8
9
10
   */
  
  #include <linux/module.h>
29fa06c12   Rudolf Marek   hwmon: New driver...
11
12
  #include <linux/init.h>
  #include <linux/slab.h>
29fa06c12   Rudolf Marek   hwmon: New driver...
13
14
  #include <linux/pci.h>
  #include <linux/hwmon.h>
29fa06c12   Rudolf Marek   hwmon: New driver...
15
16
  #include <linux/err.h>
  #include <linux/mutex.h>
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
17
  #include <asm/processor.h>
29fa06c12   Rudolf Marek   hwmon: New driver...
18
19
20
21
22
23
24
  
  #define TEMP_FROM_REG(val)	(((((val) >> 16) & 0xff) - 49) * 1000)
  #define REG_TEMP	0xe4
  #define SEL_PLACE	0x40
  #define SEL_CORE	0x04
  
  struct k8temp_data {
29fa06c12   Rudolf Marek   hwmon: New driver...
25
  	struct mutex update_lock;
29fa06c12   Rudolf Marek   hwmon: New driver...
26
27
  
  	/* registers values */
93092a644   Frans Meulenbroeks   hwmon: (k8temp) f...
28
  	u8 sensorsp;		/* sensor presence bits - SEL_CORE, SEL_PLACE */
a2e066bba   Andreas Herrmann   hwmon: (k8temp) F...
29
  	u8 swap_core_select;    /* meaning of SEL_CORE is inverted */
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
30
  	u32 temp_offset;
29fa06c12   Rudolf Marek   hwmon: New driver...
31
  };
cd9bb0564   Jingoo Han   hwmon: remove DEF...
32
  static const struct pci_device_id k8temp_ids[] = {
29fa06c12   Rudolf Marek   hwmon: New driver...
33
34
35
  	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_NB_MISC) },
  	{ 0 },
  };
b17ebc940   Jean Delvare   k8temp: Enable au...
36
  MODULE_DEVICE_TABLE(pci, k8temp_ids);
6c931ae1c   Bill Pemberton   hwmon: remove use...
37
  static int is_rev_g_desktop(u8 model)
a05e93f3b   Andreas Herrmann   hwmon: (k8temp) D...
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  {
  	u32 brandidx;
  
  	if (model < 0x69)
  		return 0;
  
  	if (model == 0xc1 || model == 0x6c || model == 0x7c)
  		return 0;
  
  	/*
  	 * Differentiate between AM2 and ASB1.
  	 * See "Constructing the processor Name String" in "Revision
  	 * Guide for AMD NPT Family 0Fh Processors" (33610).
  	 */
  	brandidx = cpuid_ebx(0x80000001);
  	brandidx = (brandidx >> 9) & 0x1f;
  
  	/* Single core */
  	if ((model == 0x6f || model == 0x7f) &&
  	    (brandidx == 0x7 || brandidx == 0x9 || brandidx == 0xc))
  		return 0;
  
  	/* Dual core */
  	if (model == 0x6b &&
  	    (brandidx == 0xb || brandidx == 0xc))
  		return 0;
  
  	return 1;
  }
3b07a702c   Robert Karszniewicz   hwmon: (k8temp) u...
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  static umode_t
  k8temp_is_visible(const void *drvdata, enum hwmon_sensor_types type,
  		  u32 attr, int channel)
  {
  	const struct k8temp_data *data = drvdata;
  
  	if ((channel & 1) && !(data->sensorsp & SEL_PLACE))
  		return 0;
  
  	if ((channel & 2) && !(data->sensorsp & SEL_CORE))
  		return 0;
  
  	return 0444;
  }
  
  static int
  k8temp_read(struct device *dev, enum hwmon_sensor_types type,
  	    u32 attr, int channel, long *val)
  {
  	struct k8temp_data *data = dev_get_drvdata(dev);
  	struct pci_dev *pdev = to_pci_dev(dev->parent);
  	int core, place;
  	u32 temp;
  	u8 tmp;
  
  	core = (channel >> 1) & 1;
  	place = channel & 1;
  
  	core ^= data->swap_core_select;
  
  	mutex_lock(&data->update_lock);
  	pci_read_config_byte(pdev, REG_TEMP, &tmp);
  	tmp &= ~(SEL_PLACE | SEL_CORE);
  	if (core)
  		tmp |= SEL_CORE;
  	if (place)
  		tmp |= SEL_PLACE;
  	pci_write_config_byte(pdev, REG_TEMP, tmp);
  	pci_read_config_dword(pdev, REG_TEMP, &temp);
  	mutex_unlock(&data->update_lock);
  
  	*val = TEMP_FROM_REG(temp) + data->temp_offset;
  
  	return 0;
  }
  
  static const struct hwmon_ops k8temp_ops = {
  	.is_visible = k8temp_is_visible,
  	.read = k8temp_read,
  };
  
  static const struct hwmon_channel_info *k8temp_info[] = {
  	HWMON_CHANNEL_INFO(temp,
  		HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT, HWMON_T_INPUT),
  	NULL
  };
  
  static const struct hwmon_chip_info k8temp_chip_info = {
  	.ops = &k8temp_ops,
  	.info = k8temp_info,
  };
6c931ae1c   Bill Pemberton   hwmon: remove use...
128
  static int k8temp_probe(struct pci_dev *pdev,
29fa06c12   Rudolf Marek   hwmon: New driver...
129
130
  				  const struct pci_device_id *id)
  {
29fa06c12   Rudolf Marek   hwmon: New driver...
131
132
  	u8 scfg;
  	u32 temp;
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
133
  	u8 model, stepping;
29fa06c12   Rudolf Marek   hwmon: New driver...
134
  	struct k8temp_data *data;
3b07a702c   Robert Karszniewicz   hwmon: (k8temp) u...
135
  	struct device *hwmon_dev;
29fa06c12   Rudolf Marek   hwmon: New driver...
136

a0d44cbcc   Guenter Roeck   hwmon: (k8temp) C...
137
138
139
  	data = devm_kzalloc(&pdev->dev, sizeof(struct k8temp_data), GFP_KERNEL);
  	if (!data)
  		return -ENOMEM;
29fa06c12   Rudolf Marek   hwmon: New driver...
140

bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
141
  	model = boot_cpu_data.x86_model;
b399151cb   Jia Zhang   x86/cpu: Rename c...
142
  	stepping = boot_cpu_data.x86_stepping;
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
143

628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
144
  	/* feature available since SH-C0, exclude older revisions */
a0d44cbcc   Guenter Roeck   hwmon: (k8temp) C...
145
146
147
  	if ((model == 4 && stepping == 0) ||
  	    (model == 5 && stepping <= 1))
  		return -ENODEV;
76ff08da3   Andreas Herrmann   hwmon: (k8temp) F...
148

628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
149
150
151
152
153
154
  	/*
  	 * AMD NPT family 0fh, i.e. RevF and RevG:
  	 * meaning of SEL_CORE bit is inverted
  	 */
  	if (model >= 0x40) {
  		data->swap_core_select = 1;
b55f37572   Guenter Roeck   hwmon: Fix checkp...
155
156
157
  		dev_warn(&pdev->dev,
  			 "Temperature readouts might be wrong - check erratum #141
  ");
bb9a35f29   Andreas Herrmann   hwmon: (k8temp) W...
158
  	}
628b4504c   Andreas Herrmann   hwmon: (k8temp) R...
159
160
161
162
163
164
165
  	/*
  	 * RevG desktop CPUs (i.e. no socket S1G1 or ASB1 parts) need
  	 * additional offset, otherwise reported temperature is below
  	 * ambient temperature
  	 */
  	if (is_rev_g_desktop(model))
  		data->temp_offset = 21000;
29fa06c12   Rudolf Marek   hwmon: New driver...
166
  	pci_read_config_byte(pdev, REG_TEMP, &scfg);
93092a644   Frans Meulenbroeks   hwmon: (k8temp) f...
167
  	scfg &= ~(SEL_PLACE | SEL_CORE);	/* Select sensor 0, core0 */
29fa06c12   Rudolf Marek   hwmon: New driver...
168
169
170
171
172
173
  	pci_write_config_byte(pdev, REG_TEMP, scfg);
  	pci_read_config_byte(pdev, REG_TEMP, &scfg);
  
  	if (scfg & (SEL_PLACE | SEL_CORE)) {
  		dev_err(&pdev->dev, "Configuration bit(s) stuck at 1!
  ");
a0d44cbcc   Guenter Roeck   hwmon: (k8temp) C...
174
  		return -ENODEV;
29fa06c12   Rudolf Marek   hwmon: New driver...
175
176
177
178
179
180
181
182
183
184
185
186
187
  	}
  
  	scfg |= (SEL_PLACE | SEL_CORE);
  	pci_write_config_byte(pdev, REG_TEMP, scfg);
  
  	/* now we know if we can change core and/or sensor */
  	pci_read_config_byte(pdev, REG_TEMP, &data->sensorsp);
  
  	if (data->sensorsp & SEL_PLACE) {
  		scfg &= ~SEL_CORE;	/* Select sensor 1, core0 */
  		pci_write_config_byte(pdev, REG_TEMP, scfg);
  		pci_read_config_dword(pdev, REG_TEMP, &temp);
  		scfg |= SEL_CORE;	/* prepare for next selection */
93092a644   Frans Meulenbroeks   hwmon: (k8temp) f...
188
  		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
29fa06c12   Rudolf Marek   hwmon: New driver...
189
190
191
192
193
194
195
  			data->sensorsp &= ~SEL_PLACE;
  	}
  
  	if (data->sensorsp & SEL_CORE) {
  		scfg &= ~SEL_PLACE;	/* Select sensor 0, core1 */
  		pci_write_config_byte(pdev, REG_TEMP, scfg);
  		pci_read_config_dword(pdev, REG_TEMP, &temp);
93092a644   Frans Meulenbroeks   hwmon: (k8temp) f...
196
  		if (!((temp >> 16) & 0xff)) /* if temp is 0 -49C is unlikely */
29fa06c12   Rudolf Marek   hwmon: New driver...
197
198
  			data->sensorsp &= ~SEL_CORE;
  	}
29fa06c12   Rudolf Marek   hwmon: New driver...
199
  	mutex_init(&data->update_lock);
29fa06c12   Rudolf Marek   hwmon: New driver...
200

3b07a702c   Robert Karszniewicz   hwmon: (k8temp) u...
201
202
203
204
205
  	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
  							 "k8temp",
  							 data,
  							 &k8temp_chip_info,
  							 NULL);
29fa06c12   Rudolf Marek   hwmon: New driver...
206

3b07a702c   Robert Karszniewicz   hwmon: (k8temp) u...
207
  	return PTR_ERR_OR_ZERO(hwmon_dev);
29fa06c12   Rudolf Marek   hwmon: New driver...
208
209
210
211
212
213
  }
  
  static struct pci_driver k8temp_driver = {
  	.name = "k8temp",
  	.id_table = k8temp_ids,
  	.probe = k8temp_probe,
29fa06c12   Rudolf Marek   hwmon: New driver...
214
  };
f71f5a550   Axel Lin   hwmon: use module...
215
  module_pci_driver(k8temp_driver);
29fa06c12   Rudolf Marek   hwmon: New driver...
216

7188cc66b   Jean Delvare   hwmon: Update Rud...
217
  MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
29fa06c12   Rudolf Marek   hwmon: New driver...
218
219
  MODULE_DESCRIPTION("AMD K8 core temperature monitor");
  MODULE_LICENSE("GPL");