Linux-libre 5.4.47-gnu
[librecmc/linux-libre.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, -1, cs5535_mfd_cells,
131                               ARRAY_SIZE(cs5535_mfd_cells), NULL, 0, NULL);
132         if (err) {
133                 dev_err(&pdev->dev, "MFD add devices failed: %d\n", err);
134                 goto err_disable;
135         }
136
137         if (machine_is_olpc())
138                 mfd_clone_cell("cs5535-acpi", olpc_acpi_clones, ARRAY_SIZE(olpc_acpi_clones));
139
140         dev_info(&pdev->dev, "%zu devices registered.\n",
141                         ARRAY_SIZE(cs5535_mfd_cells));
142
143         return 0;
144
145 err_disable:
146         pci_disable_device(pdev);
147         return err;
148 }
149
150 static void cs5535_mfd_remove(struct pci_dev *pdev)
151 {
152         mfd_remove_devices(&pdev->dev);
153         pci_disable_device(pdev);
154 }
155
156 static const struct pci_device_id cs5535_mfd_pci_tbl[] = {
157         { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) },
158         { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) },
159         { 0, }
160 };
161 MODULE_DEVICE_TABLE(pci, cs5535_mfd_pci_tbl);
162
163 static struct pci_driver cs5535_mfd_driver = {
164         .name = DRV_NAME,
165         .id_table = cs5535_mfd_pci_tbl,
166         .probe = cs5535_mfd_probe,
167         .remove = cs5535_mfd_remove,
168 };
169
170 module_pci_driver(cs5535_mfd_driver);
171
172 MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>");
173 MODULE_DESCRIPTION("MFD driver for CS5535/CS5536 southbridge's ISA PCI device");
174 MODULE_LICENSE("GPL");