cda7f5b942e7bde29360451209b47b75eb928e4d
[sfrench/cifs-2.6.git] / drivers / mfd / cs5535-mfd.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * cs5535-mfd.c - core MFD driver for CS5535/CS5536 southbridges
4  *
5  * The CS5535 and CS5536 has an ISA bridge on the PCI bus that is
6  * used for accessing GPIOs, MFGPTs, ACPI, etc.  Each subdevice has
7  * an IO range that's specified in a single BAR.  The BAR order is
8  * hardcoded in the CS553x specifications.
9  *
10  * Copyright (c) 2010  Andres Salomon <dilinger@queued.net>
11  */
12
13 #include <linux/kernel.h>
14 #include <linux/mfd/core.h>
15 #include <linux/module.h>
16 #include <linux/pci.h>
17 #include <asm/olpc.h>
18
19 #define DRV_NAME "cs5535-mfd"
20
21 enum cs5535_mfd_bars {
22         SMB_BAR = 0,
23         GPIO_BAR = 1,
24         MFGPT_BAR = 2,
25         PMS_BAR = 4,
26         ACPI_BAR = 5,
27         NR_BARS,
28 };
29
30 static int cs5535_mfd_res_enable(struct platform_device *pdev)
31 {
32         struct resource *res;
33
34         res = platform_get_resource(pdev, IORESOURCE_IO, 0);
35         if (!res) {
36                 dev_err(&pdev->dev, "can't fetch device resource info\n");
37                 return -EIO;
38         }
39
40         if (!request_region(res->start, resource_size(res), DRV_NAME)) {
41                 dev_err(&pdev->dev, "can't request region\n");
42                 return -EIO;
43         }
44
45         return 0;
46 }
47
48 static int cs5535_mfd_res_disable(struct platform_device *pdev)
49 {
50         struct resource *res;
51
52         res = platform_get_resource(pdev, IORESOURCE_IO, 0);
53         if (!res) {
54                 dev_err(&pdev->dev, "can't fetch device resource info\n");
55                 return -EIO;
56         }
57
58         release_region(res->start, resource_size(res));
59         return 0;
60 }
61
62 static struct resource cs5535_mfd_resources[NR_BARS];
63
64 static struct mfd_cell cs5535_mfd_cells[] = {
65         {
66                 .id = SMB_BAR,
67                 .name = "cs5535-smb",
68                 .num_resources = 1,
69                 .resources = &cs5535_mfd_resources[SMB_BAR],
70         },
71         {
72                 .id = GPIO_BAR,
73                 .name = "cs5535-gpio",
74                 .num_resources = 1,
75                 .resources = &cs5535_mfd_resources[GPIO_BAR],
76         },
77         {
78                 .id = MFGPT_BAR,
79                 .name = "cs5535-mfgpt",
80                 .num_resources = 1,
81                 .resources = &cs5535_mfd_resources[MFGPT_BAR],
82         },
83         {
84                 .id = PMS_BAR,
85                 .name = "cs5535-pms",
86                 .num_resources = 1,
87                 .resources = &cs5535_mfd_resources[PMS_BAR],
88
89                 .enable = cs5535_mfd_res_enable,
90                 .disable = cs5535_mfd_res_disable,
91         },
92         {
93                 .id = ACPI_BAR,
94                 .name = "cs5535-acpi",
95                 .num_resources = 1,
96                 .resources = &cs5535_mfd_resources[ACPI_BAR],
97
98                 .enable = cs5535_mfd_res_enable,
99                 .disable = cs5535_mfd_res_disable,
100         },
101 };
102
103 static const char *olpc_acpi_clones[] = {
104         "olpc-xo1-pm-acpi",
105         "olpc-xo1-sci-acpi"
106 };
107
108 static int cs5535_mfd_probe(struct pci_dev *pdev,
109                 const struct pci_device_id *id)
110 {
111         int err, i;
112
113         err = pci_enable_device(pdev);
114         if (err)
115                 return err;
116
117         /* fill in IO range for each cell; subdrivers handle the region */
118         for (i = 0; i < ARRAY_SIZE(cs5535_mfd_cells); i++) {
119                 int bar = cs5535_mfd_cells[i].id;
120                 struct resource *r = &cs5535_mfd_resources[bar];
121
122                 r->flags = IORESOURCE_IO;
123                 r->start = pci_resource_start(pdev, bar);
124                 r->end = pci_resource_end(pdev, bar);
125
126                 /* id is used for temporarily storing BAR; unset it now */
127                 cs5535_mfd_cells[i].id = 0;
128         }
129
130         err = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, cs5535_mfd_cells,
131                               ARRAY_SIZE(cs5535_mfd_cells), NULL, 0, NULL);
132         if (err) {
133                 dev_err(&pdev->dev,
134                         "Failed to add CS5535 sub-devices: %d\n", err);
135                 goto err_disable;
136         }
137
138         if (machine_is_olpc())
139                 mfd_clone_cell("cs5535-acpi", olpc_acpi_clones, ARRAY_SIZE(olpc_acpi_clones));
140
141         dev_info(&pdev->dev, "%zu devices registered.\n",
142                         ARRAY_SIZE(cs5535_mfd_cells));
143
144         return 0;
145
146 err_disable:
147         pci_disable_device(pdev);
148         return err;
149 }
150
151 static void cs5535_mfd_remove(struct pci_dev *pdev)
152 {
153         mfd_remove_devices(&pdev->dev);
154         pci_disable_device(pdev);
155 }
156
157 static const struct pci_device_id cs5535_mfd_pci_tbl[] = {
158         { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) },
159         { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) },
160         { 0, }
161 };
162 MODULE_DEVICE_TABLE(pci, cs5535_mfd_pci_tbl);
163
164 static struct pci_driver cs5535_mfd_driver = {
165         .name = DRV_NAME,
166         .id_table = cs5535_mfd_pci_tbl,
167         .probe = cs5535_mfd_probe,
168         .remove = cs5535_mfd_remove,
169 };
170
171 module_pci_driver(cs5535_mfd_driver);
172
173 MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
174 MODULE_DESCRIPTION("MFD driver for CS5535/CS5536 southbridge's ISA PCI device");
175 MODULE_LICENSE("GPL");