1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) Intel Corp. 2007.
4  * All Rights Reserved.
5  *
6  * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
7  * develop this driver.
8  *
9  * This file is part of the Carillo Ranch video subsystem driver.
10  *
11  * Authors:
12  *   Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
13  *   Alan Hourihane <alanh-at-tungstengraphics-dot-com>
14  */
15 
16 #include <linux/module.h>
17 #include <linux/kernel.h>
18 #include <linux/pci.h>
19 #include <linux/errno.h>
20 #include <linux/fb.h>
21 #include "vermilion.h"
22 
23 /* The PLL Clock register sits on Host bridge */
24 #define CRVML_DEVICE_MCH   0x5001
25 #define CRVML_REG_MCHBAR   0x44
26 #define CRVML_REG_MCHEN    0x54
27 #define CRVML_MCHEN_BIT    (1 << 28)
28 #define CRVML_MCHMAP_SIZE  4096
29 #define CRVML_REG_CLOCK    0xc3c
30 #define CRVML_CLOCK_SHIFT  8
31 #define CRVML_CLOCK_MASK   0x00000f00
32 
33 static struct pci_dev *mch_dev;
34 static u32 mch_bar;
35 static void __iomem *mch_regs_base;
36 static u32 saved_clock;
37 
38 static const unsigned crvml_clocks[] = {
39 	6750,
40 	13500,
41 	27000,
42 	29700,
43 	37125,
44 	54000,
45 	59400,
46 	74250,
47 	120000
48 	    /*
49 	     * There are more clocks, but they are disabled on the CR board.
50 	     */
51 };
52 
53 static const u32 crvml_clock_bits[] = {
54 	0x0a,
55 	0x09,
56 	0x08,
57 	0x07,
58 	0x06,
59 	0x05,
60 	0x04,
61 	0x03,
62 	0x0b
63 };
64 
65 static const unsigned crvml_num_clocks = ARRAY_SIZE(crvml_clocks);
66 
67 static int crvml_sys_restore(struct vml_sys *sys)
68 {
69 	void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK;
70 
71 	iowrite32(saved_clock, clock_reg);
72 	ioread32(clock_reg);
73 
74 	return 0;
75 }
76 
77 static int crvml_sys_save(struct vml_sys *sys)
78 {
79 	void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK;
80 
81 	saved_clock = ioread32(clock_reg);
82 
83 	return 0;
84 }
85 
86 static int crvml_nearest_index(const struct vml_sys *sys, int clock)
87 {
88 	int i;
89 	int cur_index = 0;
90 	int cur_diff;
91 	int diff;
92 
93 	cur_diff = clock - crvml_clocks[0];
94 	cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff;
95 	for (i = 1; i < crvml_num_clocks; ++i) {
96 		diff = clock - crvml_clocks[i];
97 		diff = (diff < 0) ? -diff : diff;
98 		if (diff < cur_diff) {
99 			cur_index = i;
100 			cur_diff = diff;
101 		}
102 	}
103 	return cur_index;
104 }
105 
106 static int crvml_nearest_clock(const struct vml_sys *sys, int clock)
107 {
108 	return crvml_clocks[crvml_nearest_index(sys, clock)];
109 }
110 
111 static int crvml_set_clock(struct vml_sys *sys, int clock)
112 {
113 	void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK;
114 	int index;
115 	u32 clock_val;
116 
117 	index = crvml_nearest_index(sys, clock);
118 
119 	if (crvml_clocks[index] != clock)
120 		return -EINVAL;
121 
122 	clock_val = ioread32(clock_reg) & ~CRVML_CLOCK_MASK;
123 	clock_val = crvml_clock_bits[index] << CRVML_CLOCK_SHIFT;
124 	iowrite32(clock_val, clock_reg);
125 	ioread32(clock_reg);
126 
127 	return 0;
128 }
129 
130 static struct vml_sys cr_pll_ops = {
131 	.name = "Carillo Ranch",
132 	.save = crvml_sys_save,
133 	.restore = crvml_sys_restore,
134 	.set_clock = crvml_set_clock,
135 	.nearest_clock = crvml_nearest_clock,
136 };
137 
138 static int __init cr_pll_init(void)
139 {
140 	int err;
141 	u32 dev_en;
142 
143 	mch_dev = pci_get_device(PCI_VENDOR_ID_INTEL,
144 					CRVML_DEVICE_MCH, NULL);
145 	if (!mch_dev) {
146 		printk(KERN_ERR
147 		       "Could not find Carillo Ranch MCH device.\n");
148 		return -ENODEV;
149 	}
150 
151 	pci_read_config_dword(mch_dev, CRVML_REG_MCHEN, &dev_en);
152 	if (!(dev_en & CRVML_MCHEN_BIT)) {
153 		printk(KERN_ERR
154 		       "Carillo Ranch MCH device was not enabled.\n");
155 		pci_dev_put(mch_dev);
156 		return -ENODEV;
157 	}
158 
159 	pci_read_config_dword(mch_dev, CRVML_REG_MCHBAR,
160 			      &mch_bar);
161 	mch_regs_base =
162 	    ioremap(mch_bar, CRVML_MCHMAP_SIZE);
163 	if (!mch_regs_base) {
164 		printk(KERN_ERR
165 		       "Carillo Ranch MCH device was not enabled.\n");
166 		pci_dev_put(mch_dev);
167 		return -ENODEV;
168 	}
169 
170 	err = vmlfb_register_subsys(&cr_pll_ops);
171 	if (err) {
172 		printk(KERN_ERR
173 		       "Carillo Ranch failed to initialize vml_sys.\n");
174 		iounmap(mch_regs_base);
175 		pci_dev_put(mch_dev);
176 		return err;
177 	}
178 
179 	return 0;
180 }
181 
182 static void __exit cr_pll_exit(void)
183 {
184 	vmlfb_unregister_subsys(&cr_pll_ops);
185 
186 	iounmap(mch_regs_base);
187 	pci_dev_put(mch_dev);
188 }
189 
190 module_init(cr_pll_init);
191 module_exit(cr_pll_exit);
192 
193 MODULE_AUTHOR("Tungsten Graphics Inc.");
194 MODULE_DESCRIPTION("Carillo Ranch PLL Driver");
195 MODULE_LICENSE("GPL");
196