diff options
author | Bartlomiej Zolnierkiewicz <bzolnier@gmail.com> | 2009-02-02 14:12:23 -0500 |
---|---|---|
committer | Bartlomiej Zolnierkiewicz <bzolnier@gmail.com> | 2009-02-02 14:12:23 -0500 |
commit | a77dcc437c1c3bc73887ecac8a304e4adcabb9b7 (patch) | |
tree | f4e87dd4be440be29f819dff169d5c9b6724558a | |
parent | 9f6514c1c5b55ab90f3ad1f3fc18b9daa5bd9c8c (diff) |
ide: add CS5536 host driver (v3)
This is a port of libata's pata_cs5536.c (written by Martin K. Petersen)
to IDE subsystem.
Changes done while at it:
* Reprogram PIO/MWDMA timings if needed before and after DMA transfer
(chipset uses shared PIO/MWDMA timings).
* Fix cable detection to report 80-wires cable if BIOS set it for any
device on a port (IDE core will do drive-side cable detection later).
* Don't disable UDMA while programming PIO timings.
* Simplify PCI/MSR support.
Pros of having IDE host driver in addition to libata's one:
* IDE is much lighter than SCSI+libata, the host driver itself is also
a bit smaller:
text data bss dec hex filename
1261 496 4 1761 6e1 drivers/ata/pata_cs5536.o
1242 128 4 1374 55e drivers/ide/cs5536.o
* This allows use of IDE features which are unavailable under libata.
v2:
* Fixes per review from Sergei:
- simplify dependency check in Kconfig
- use IDE_DRV_MASK also for ->drive_data
- disable UDMA when programming MWDMA
- program new DTC timings only when necessary
- fix printk() level in cs5536_init_one()
* Fix patch description according to comments from Alan and Sergei.
v3:
* Smarter masking of UDMA bits per Sergei's suggestion.
Cc: Martin K. Petersen <mkp@mkp.net>
Cc: Karl Auerbach <karl@iwl.com>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Acked-by: Sergei Shtylyov <sshtylyov@ru.mvista.com>
Signed-off-by: Bartlomiej Zolnierkiewicz <bzolnier@gmail.com>
-rw-r--r-- | drivers/ide/Kconfig | 10 | ||||
-rw-r--r-- | drivers/ide/Makefile | 1 | ||||
-rw-r--r-- | drivers/ide/cs5536.c | 308 |
3 files changed, 319 insertions, 0 deletions
diff --git a/drivers/ide/Kconfig b/drivers/ide/Kconfig index b1c6f68d98c..3dad2299d9c 100644 --- a/drivers/ide/Kconfig +++ b/drivers/ide/Kconfig | |||
@@ -465,6 +465,16 @@ config BLK_DEV_CS5535 | |||
465 | 465 | ||
466 | It is safe to say Y to this question. | 466 | It is safe to say Y to this question. |
467 | 467 | ||
468 | config BLK_DEV_CS5536 | ||
469 | tristate "CS5536 chipset support" | ||
470 | depends on X86_32 | ||
471 | select BLK_DEV_IDEDMA_PCI | ||
472 | help | ||
473 | This option enables support for the AMD CS5536 | ||
474 | companion chip used with the Geode LX processor family. | ||
475 | |||
476 | If unsure, say N. | ||
477 | |||
468 | config BLK_DEV_HPT366 | 478 | config BLK_DEV_HPT366 |
469 | tristate "HPT36X/37X chipset support" | 479 | tristate "HPT36X/37X chipset support" |
470 | select BLK_DEV_IDEDMA_PCI | 480 | select BLK_DEV_IDEDMA_PCI |
diff --git a/drivers/ide/Makefile b/drivers/ide/Makefile index c2b9c93f009..d0e3d7d5b46 100644 --- a/drivers/ide/Makefile +++ b/drivers/ide/Makefile | |||
@@ -43,6 +43,7 @@ obj-$(CONFIG_BLK_DEV_CMD64X) += cmd64x.o | |||
43 | obj-$(CONFIG_BLK_DEV_CS5520) += cs5520.o | 43 | obj-$(CONFIG_BLK_DEV_CS5520) += cs5520.o |
44 | obj-$(CONFIG_BLK_DEV_CS5530) += cs5530.o | 44 | obj-$(CONFIG_BLK_DEV_CS5530) += cs5530.o |
45 | obj-$(CONFIG_BLK_DEV_CS5535) += cs5535.o | 45 | obj-$(CONFIG_BLK_DEV_CS5535) += cs5535.o |
46 | obj-$(CONFIG_BLK_DEV_CS5536) += cs5536.o | ||
46 | obj-$(CONFIG_BLK_DEV_SC1200) += sc1200.o | 47 | obj-$(CONFIG_BLK_DEV_SC1200) += sc1200.o |
47 | obj-$(CONFIG_BLK_DEV_CY82C693) += cy82c693.o | 48 | obj-$(CONFIG_BLK_DEV_CY82C693) += cy82c693.o |
48 | obj-$(CONFIG_BLK_DEV_DELKIN) += delkin_cb.o | 49 | obj-$(CONFIG_BLK_DEV_DELKIN) += delkin_cb.o |
diff --git a/drivers/ide/cs5536.c b/drivers/ide/cs5536.c new file mode 100644 index 00000000000..7a62db719a4 --- /dev/null +++ b/drivers/ide/cs5536.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | * CS5536 PATA support | ||
3 | * (C) 2007 Martin K. Petersen <mkp@mkp.net> | ||
4 | * (C) 2009 Bartlomiej Zolnierkiewicz | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
18 | * | ||
19 | * Documentation: | ||
20 | * Available from AMD web site. | ||
21 | * | ||
22 | * The IDE timing registers for the CS5536 live in the Geode Machine | ||
23 | * Specific Register file and not PCI config space. Most BIOSes | ||
24 | * virtualize the PCI registers so the chip looks like a standard IDE | ||
25 | * controller. Unfortunately not all implementations get this right. | ||
26 | * In particular some have problems with unaligned accesses to the | ||
27 | * virtualized PCI registers. This driver always does full dword | ||
28 | * writes to work around the issue. Also, in case of a bad BIOS this | ||
29 | * driver can be loaded with the "msr=1" parameter which forces using | ||
30 | * the Machine Specific Registers to configure the device. | ||
31 | */ | ||
32 | |||
33 | #include <linux/kernel.h> | ||
34 | #include <linux/module.h> | ||
35 | #include <linux/pci.h> | ||
36 | #include <linux/init.h> | ||
37 | #include <linux/ide.h> | ||
38 | #include <asm/msr.h> | ||
39 | |||
40 | #define DRV_NAME "cs5536" | ||
41 | |||
42 | enum { | ||
43 | MSR_IDE_CFG = 0x51300010, | ||
44 | PCI_IDE_CFG = 0x40, | ||
45 | |||
46 | CFG = 0, | ||
47 | DTC = 2, | ||
48 | CAST = 3, | ||
49 | ETC = 4, | ||
50 | |||
51 | IDE_CFG_CHANEN = (1 << 1), | ||
52 | IDE_CFG_CABLE = (1 << 17) | (1 << 16), | ||
53 | |||
54 | IDE_D0_SHIFT = 24, | ||
55 | IDE_D1_SHIFT = 16, | ||
56 | IDE_DRV_MASK = 0xff, | ||
57 | |||
58 | IDE_CAST_D0_SHIFT = 6, | ||
59 | IDE_CAST_D1_SHIFT = 4, | ||
60 | IDE_CAST_DRV_MASK = 0x3, | ||
61 | |||
62 | IDE_CAST_CMD_SHIFT = 24, | ||
63 | IDE_CAST_CMD_MASK = 0xff, | ||
64 | |||
65 | IDE_ETC_UDMA_MASK = 0xc0, | ||
66 | }; | ||
67 | |||
68 | static int use_msr; | ||
69 | |||
70 | static int cs5536_read(struct pci_dev *pdev, int reg, u32 *val) | ||
71 | { | ||
72 | if (unlikely(use_msr)) { | ||
73 | u32 dummy; | ||
74 | |||
75 | rdmsr(MSR_IDE_CFG + reg, *val, dummy); | ||
76 | return 0; | ||
77 | } | ||
78 | |||
79 | return pci_read_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); | ||
80 | } | ||
81 | |||
82 | static int cs5536_write(struct pci_dev *pdev, int reg, int val) | ||
83 | { | ||
84 | if (unlikely(use_msr)) { | ||
85 | wrmsr(MSR_IDE_CFG + reg, val, 0); | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | return pci_write_config_dword(pdev, PCI_IDE_CFG + reg * 4, val); | ||
90 | } | ||
91 | |||
92 | static void cs5536_program_dtc(ide_drive_t *drive, u8 tim) | ||
93 | { | ||
94 | struct pci_dev *pdev = to_pci_dev(drive->hwif->dev); | ||
95 | int dshift = (drive->dn & 1) ? IDE_D1_SHIFT : IDE_D0_SHIFT; | ||
96 | u32 dtc; | ||
97 | |||
98 | cs5536_read(pdev, DTC, &dtc); | ||
99 | dtc &= ~(IDE_DRV_MASK << dshift); | ||
100 | dtc |= tim << dshift; | ||
101 | cs5536_write(pdev, DTC, dtc); | ||
102 | } | ||
103 | |||
104 | /** | ||
105 | * cs5536_cable_detect - detect cable type | ||
106 | * @hwif: Port to detect on | ||
107 | * | ||
108 | * Perform cable detection for ATA66 capable cable. | ||
109 | * | ||
110 | * Returns a cable type. | ||
111 | */ | ||
112 | |||
113 | static u8 cs5536_cable_detect(ide_hwif_t *hwif) | ||
114 | { | ||
115 | struct pci_dev *pdev = to_pci_dev(hwif->dev); | ||
116 | u32 cfg; | ||
117 | |||
118 | cs5536_read(pdev, CFG, &cfg); | ||
119 | |||
120 | if (cfg & IDE_CFG_CABLE) | ||
121 | return ATA_CBL_PATA80; | ||
122 | else | ||
123 | return ATA_CBL_PATA40; | ||
124 | } | ||
125 | |||
126 | /** | ||
127 | * cs5536_set_pio_mode - PIO timing setup | ||
128 | * @drive: ATA device | ||
129 | * @pio: PIO mode number | ||
130 | */ | ||
131 | |||
132 | static void cs5536_set_pio_mode(ide_drive_t *drive, const u8 pio) | ||
133 | { | ||
134 | static const u8 drv_timings[5] = { | ||
135 | 0x98, 0x55, 0x32, 0x21, 0x20, | ||
136 | }; | ||
137 | |||
138 | static const u8 addr_timings[5] = { | ||
139 | 0x2, 0x1, 0x0, 0x0, 0x0, | ||
140 | }; | ||
141 | |||
142 | static const u8 cmd_timings[5] = { | ||
143 | 0x99, 0x92, 0x90, 0x22, 0x20, | ||
144 | }; | ||
145 | |||
146 | struct pci_dev *pdev = to_pci_dev(drive->hwif->dev); | ||
147 | ide_drive_t *pair = ide_get_pair_dev(drive); | ||
148 | int cshift = (drive->dn & 1) ? IDE_CAST_D1_SHIFT : IDE_CAST_D0_SHIFT; | ||
149 | u32 cast; | ||
150 | u8 cmd_pio = pio; | ||
151 | |||
152 | if (pair) | ||
153 | cmd_pio = min(pio, ide_get_best_pio_mode(pair, 255, 4)); | ||
154 | |||
155 | drive->drive_data &= (IDE_DRV_MASK << 8); | ||
156 | drive->drive_data |= drv_timings[pio]; | ||
157 | |||
158 | cs5536_program_dtc(drive, drv_timings[pio]); | ||
159 | |||
160 | cs5536_read(pdev, CAST, &cast); | ||
161 | |||
162 | cast &= ~(IDE_CAST_DRV_MASK << cshift); | ||
163 | cast |= addr_timings[pio] << cshift; | ||
164 | |||
165 | cast &= ~(IDE_CAST_CMD_MASK << IDE_CAST_CMD_SHIFT); | ||
166 | cast |= cmd_timings[cmd_pio] << IDE_CAST_CMD_SHIFT; | ||
167 | |||
168 | cs5536_write(pdev, CAST, cast); | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * cs5536_set_dma_mode - DMA timing setup | ||
173 | * @drive: ATA device | ||
174 | * @mode: DMA mode | ||
175 | */ | ||
176 | |||
177 | static void cs5536_set_dma_mode(ide_drive_t *drive, const u8 mode) | ||
178 | { | ||
179 | static const u8 udma_timings[6] = { | ||
180 | 0xc2, 0xc1, 0xc0, 0xc4, 0xc5, 0xc6, | ||
181 | }; | ||
182 | |||
183 | static const u8 mwdma_timings[3] = { | ||
184 | 0x67, 0x21, 0x20, | ||
185 | }; | ||
186 | |||
187 | struct pci_dev *pdev = to_pci_dev(drive->hwif->dev); | ||
188 | int dshift = (drive->dn & 1) ? IDE_D1_SHIFT : IDE_D0_SHIFT; | ||
189 | u32 etc; | ||
190 | |||
191 | cs5536_read(pdev, ETC, &etc); | ||
192 | |||
193 | if (mode >= XFER_UDMA_0) { | ||
194 | etc &= ~(IDE_DRV_MASK << dshift); | ||
195 | etc |= udma_timings[mode - XFER_UDMA_0] << dshift; | ||
196 | } else { /* MWDMA */ | ||
197 | etc &= ~(IDE_ETC_UDMA_MASK << dshift); | ||
198 | drive->drive_data &= IDE_DRV_MASK; | ||
199 | drive->drive_data |= mwdma_timings[mode - XFER_MW_DMA_0] << 8; | ||
200 | } | ||
201 | |||
202 | cs5536_write(pdev, ETC, etc); | ||
203 | } | ||
204 | |||
205 | static void cs5536_dma_start(ide_drive_t *drive) | ||
206 | { | ||
207 | if (drive->current_speed < XFER_UDMA_0 && | ||
208 | (drive->drive_data >> 8) != (drive->drive_data & IDE_DRV_MASK)) | ||
209 | cs5536_program_dtc(drive, drive->drive_data >> 8); | ||
210 | |||
211 | ide_dma_start(drive); | ||
212 | } | ||
213 | |||
214 | static int cs5536_dma_end(ide_drive_t *drive) | ||
215 | { | ||
216 | int ret = ide_dma_end(drive); | ||
217 | |||
218 | if (drive->current_speed < XFER_UDMA_0 && | ||
219 | (drive->drive_data >> 8) != (drive->drive_data & IDE_DRV_MASK)) | ||
220 | cs5536_program_dtc(drive, drive->drive_data & IDE_DRV_MASK); | ||
221 | |||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | static const struct ide_port_ops cs5536_port_ops = { | ||
226 | .set_pio_mode = cs5536_set_pio_mode, | ||
227 | .set_dma_mode = cs5536_set_dma_mode, | ||
228 | .cable_detect = cs5536_cable_detect, | ||
229 | }; | ||
230 | |||
231 | static const struct ide_dma_ops cs5536_dma_ops = { | ||
232 | .dma_host_set = ide_dma_host_set, | ||
233 | .dma_setup = ide_dma_setup, | ||
234 | .dma_exec_cmd = ide_dma_exec_cmd, | ||
235 | .dma_start = cs5536_dma_start, | ||
236 | .dma_end = cs5536_dma_end, | ||
237 | .dma_test_irq = ide_dma_test_irq, | ||
238 | .dma_lost_irq = ide_dma_lost_irq, | ||
239 | .dma_timeout = ide_dma_timeout, | ||
240 | }; | ||
241 | |||
242 | static const struct ide_port_info cs5536_info = { | ||
243 | .name = DRV_NAME, | ||
244 | .port_ops = &cs5536_port_ops, | ||
245 | .dma_ops = &cs5536_dma_ops, | ||
246 | .host_flags = IDE_HFLAG_SINGLE, | ||
247 | .pio_mask = ATA_PIO4, | ||
248 | .mwdma_mask = ATA_MWDMA2, | ||
249 | .udma_mask = ATA_UDMA5, | ||
250 | }; | ||
251 | |||
252 | /** | ||
253 | * cs5536_init_one | ||
254 | * @dev: PCI device | ||
255 | * @id: Entry in match table | ||
256 | */ | ||
257 | |||
258 | static int cs5536_init_one(struct pci_dev *dev, const struct pci_device_id *id) | ||
259 | { | ||
260 | u32 cfg; | ||
261 | |||
262 | if (use_msr) | ||
263 | printk(KERN_INFO DRV_NAME ": Using MSR regs instead of PCI\n"); | ||
264 | |||
265 | cs5536_read(dev, CFG, &cfg); | ||
266 | |||
267 | if ((cfg & IDE_CFG_CHANEN) == 0) { | ||
268 | printk(KERN_ERR DRV_NAME ": disabled by BIOS\n"); | ||
269 | return -ENODEV; | ||
270 | } | ||
271 | |||
272 | return ide_pci_init_one(dev, &cs5536_info, NULL); | ||
273 | } | ||
274 | |||
275 | static const struct pci_device_id cs5536_pci_tbl[] = { | ||
276 | { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CS5536_IDE), }, | ||
277 | { }, | ||
278 | }; | ||
279 | |||
280 | static struct pci_driver cs5536_pci_driver = { | ||
281 | .name = DRV_NAME, | ||
282 | .id_table = cs5536_pci_tbl, | ||
283 | .probe = cs5536_init_one, | ||
284 | .remove = ide_pci_remove, | ||
285 | .suspend = ide_pci_suspend, | ||
286 | .resume = ide_pci_resume, | ||
287 | }; | ||
288 | |||
289 | static int __init cs5536_init(void) | ||
290 | { | ||
291 | return pci_register_driver(&cs5536_pci_driver); | ||
292 | } | ||
293 | |||
294 | static void __exit cs5536_exit(void) | ||
295 | { | ||
296 | pci_unregister_driver(&cs5536_pci_driver); | ||
297 | } | ||
298 | |||
299 | MODULE_AUTHOR("Martin K. Petersen, Bartlomiej Zolnierkiewicz"); | ||
300 | MODULE_DESCRIPTION("low-level driver for the CS5536 IDE controller"); | ||
301 | MODULE_LICENSE("GPL"); | ||
302 | MODULE_DEVICE_TABLE(pci, cs5536_pci_tbl); | ||
303 | |||
304 | module_param_named(msr, use_msr, int, 0644); | ||
305 | MODULE_PARM_DESC(msr, "Force using MSR to configure IDE function (Default: 0)"); | ||
306 | |||
307 | module_init(cs5536_init); | ||
308 | module_exit(cs5536_exit); | ||