xref: /linux/drivers/cpufreq/sc520_freq.c (revision 02680c23d7b3febe45ea3d4f9818c2b2dc89020a)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *	sc520_freq.c: cpufreq driver for the AMD Elan sc520
4  *
5  *	Copyright (C) 2005 Sean Young <sean@mess.org>
6  *
7  *	Based on elanfreq.c
8  *
9  *	2005-03-30: - initial revision
10  */
11 
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13 
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/init.h>
17 
18 #include <linux/delay.h>
19 #include <linux/cpufreq.h>
20 #include <linux/timex.h>
21 #include <linux/io.h>
22 
23 #include <asm/cpu_device_id.h>
24 #include <asm/msr.h>
25 
26 #define MMCR_BASE	0xfffef000	/* The default base address */
27 #define OFFS_CPUCTL	0x2   /* CPU Control Register */
28 
29 static __u8 __iomem *cpuctl;
30 
31 static struct cpufreq_frequency_table sc520_freq_table[] = {
32 	{0, 0x01,	100000},
33 	{0, 0x02,	133000},
34 	{0, 0,	CPUFREQ_TABLE_END},
35 };
36 
37 static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu)
38 {
39 	u8 clockspeed_reg = *cpuctl;
40 
41 	switch (clockspeed_reg & 0x03) {
42 	default:
43 		pr_err("error: cpuctl register has unexpected value %02x\n",
44 		       clockspeed_reg);
45 	case 0x01:
46 		return 100000;
47 	case 0x02:
48 		return 133000;
49 	}
50 }
51 
52 static int sc520_freq_target(struct cpufreq_policy *policy, unsigned int state)
53 {
54 
55 	u8 clockspeed_reg;
56 
57 	local_irq_disable();
58 
59 	clockspeed_reg = *cpuctl & ~0x03;
60 	*cpuctl = clockspeed_reg | sc520_freq_table[state].driver_data;
61 
62 	local_irq_enable();
63 
64 	return 0;
65 }
66 
67 /*
68  *	Module init and exit code
69  */
70 
71 static int sc520_freq_cpu_init(struct cpufreq_policy *policy)
72 {
73 	struct cpuinfo_x86 *c = &cpu_data(0);
74 
75 	/* capability check */
76 	if (c->x86_vendor != X86_VENDOR_AMD ||
77 	    c->x86 != 4 || c->x86_model != 9)
78 		return -ENODEV;
79 
80 	/* cpuinfo and default policy values */
81 	policy->cpuinfo.transition_latency = 1000000; /* 1ms */
82 	policy->freq_table = sc520_freq_table;
83 
84 	return 0;
85 }
86 
87 
88 static struct cpufreq_driver sc520_freq_driver = {
89 	.get	= sc520_freq_get_cpu_frequency,
90 	.verify	= cpufreq_generic_frequency_table_verify,
91 	.target_index = sc520_freq_target,
92 	.init	= sc520_freq_cpu_init,
93 	.name	= "sc520_freq",
94 	.attr	= cpufreq_generic_attr,
95 };
96 
97 static const struct x86_cpu_id sc520_ids[] = {
98 	X86_MATCH_VENDOR_FAM_MODEL(AMD, 4, 9, NULL),
99 	{}
100 };
101 MODULE_DEVICE_TABLE(x86cpu, sc520_ids);
102 
103 static int __init sc520_freq_init(void)
104 {
105 	int err;
106 
107 	if (!x86_match_cpu(sc520_ids))
108 		return -ENODEV;
109 
110 	cpuctl = ioremap((unsigned long)(MMCR_BASE + OFFS_CPUCTL), 1);
111 	if (!cpuctl) {
112 		pr_err("sc520_freq: error: failed to remap memory\n");
113 		return -ENOMEM;
114 	}
115 
116 	err = cpufreq_register_driver(&sc520_freq_driver);
117 	if (err)
118 		iounmap(cpuctl);
119 
120 	return err;
121 }
122 
123 
124 static void __exit sc520_freq_exit(void)
125 {
126 	cpufreq_unregister_driver(&sc520_freq_driver);
127 	iounmap(cpuctl);
128 }
129 
130 
131 MODULE_LICENSE("GPL");
132 MODULE_AUTHOR("Sean Young <sean@mess.org>");
133 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan sc520 CPU");
134 
135 module_init(sc520_freq_init);
136 module_exit(sc520_freq_exit);
137 
138