1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014 Free Electrons |
4 | * Copyright (C) 2014 Atmel |
5 | * |
6 | * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/iopoll.h> |
11 | #include <linux/mfd/atmel-hlcdc.h> |
12 | #include <linux/mfd/core.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | #define ATMEL_HLCDC_REG_MAX (0x4000 - 0x4) |
19 | |
20 | struct atmel_hlcdc_regmap { |
21 | void __iomem *regs; |
22 | struct device *dev; |
23 | }; |
24 | |
25 | static const struct mfd_cell atmel_hlcdc_cells[] = { |
26 | { |
27 | .name = "atmel-hlcdc-pwm" , |
28 | .of_compatible = "atmel,hlcdc-pwm" , |
29 | }, |
30 | { |
31 | .name = "atmel-hlcdc-dc" , |
32 | .of_compatible = "atmel,hlcdc-display-controller" , |
33 | }, |
34 | }; |
35 | |
36 | static int regmap_atmel_hlcdc_reg_write(void *context, unsigned int reg, |
37 | unsigned int val) |
38 | { |
39 | struct atmel_hlcdc_regmap *hregmap = context; |
40 | |
41 | if (reg <= ATMEL_HLCDC_DIS) { |
42 | u32 status; |
43 | int ret; |
44 | |
45 | ret = readl_poll_timeout_atomic(hregmap->regs + ATMEL_HLCDC_SR, |
46 | status, |
47 | !(status & ATMEL_HLCDC_SIP), |
48 | 1, 100); |
49 | if (ret) { |
50 | dev_err(hregmap->dev, |
51 | "Timeout! Clock domain synchronization is in progress!\n" ); |
52 | return ret; |
53 | } |
54 | } |
55 | |
56 | writel(val, addr: hregmap->regs + reg); |
57 | |
58 | return 0; |
59 | } |
60 | |
61 | static int regmap_atmel_hlcdc_reg_read(void *context, unsigned int reg, |
62 | unsigned int *val) |
63 | { |
64 | struct atmel_hlcdc_regmap *hregmap = context; |
65 | |
66 | *val = readl(addr: hregmap->regs + reg); |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static const struct regmap_config atmel_hlcdc_regmap_config = { |
72 | .reg_bits = 32, |
73 | .val_bits = 32, |
74 | .reg_stride = 4, |
75 | .max_register = ATMEL_HLCDC_REG_MAX, |
76 | .reg_write = regmap_atmel_hlcdc_reg_write, |
77 | .reg_read = regmap_atmel_hlcdc_reg_read, |
78 | .fast_io = true, |
79 | }; |
80 | |
81 | static int atmel_hlcdc_probe(struct platform_device *pdev) |
82 | { |
83 | struct atmel_hlcdc_regmap *hregmap; |
84 | struct device *dev = &pdev->dev; |
85 | struct atmel_hlcdc *hlcdc; |
86 | |
87 | hregmap = devm_kzalloc(dev, size: sizeof(*hregmap), GFP_KERNEL); |
88 | if (!hregmap) |
89 | return -ENOMEM; |
90 | |
91 | hlcdc = devm_kzalloc(dev, size: sizeof(*hlcdc), GFP_KERNEL); |
92 | if (!hlcdc) |
93 | return -ENOMEM; |
94 | |
95 | hregmap->regs = devm_platform_ioremap_resource(pdev, index: 0); |
96 | if (IS_ERR(ptr: hregmap->regs)) |
97 | return PTR_ERR(ptr: hregmap->regs); |
98 | |
99 | hregmap->dev = &pdev->dev; |
100 | |
101 | hlcdc->irq = platform_get_irq(pdev, 0); |
102 | if (hlcdc->irq < 0) |
103 | return hlcdc->irq; |
104 | |
105 | hlcdc->periph_clk = devm_clk_get(dev, id: "periph_clk" ); |
106 | if (IS_ERR(ptr: hlcdc->periph_clk)) { |
107 | dev_err(dev, "failed to get peripheral clock\n" ); |
108 | return PTR_ERR(ptr: hlcdc->periph_clk); |
109 | } |
110 | |
111 | hlcdc->sys_clk = devm_clk_get(dev, id: "sys_clk" ); |
112 | if (IS_ERR(ptr: hlcdc->sys_clk)) { |
113 | dev_err(dev, "failed to get system clock\n" ); |
114 | return PTR_ERR(ptr: hlcdc->sys_clk); |
115 | } |
116 | |
117 | hlcdc->slow_clk = devm_clk_get(dev, id: "slow_clk" ); |
118 | if (IS_ERR(ptr: hlcdc->slow_clk)) { |
119 | dev_err(dev, "failed to get slow clock\n" ); |
120 | return PTR_ERR(ptr: hlcdc->slow_clk); |
121 | } |
122 | |
123 | hlcdc->regmap = devm_regmap_init(dev, NULL, hregmap, |
124 | &atmel_hlcdc_regmap_config); |
125 | if (IS_ERR(ptr: hlcdc->regmap)) |
126 | return PTR_ERR(ptr: hlcdc->regmap); |
127 | |
128 | dev_set_drvdata(dev, data: hlcdc); |
129 | |
130 | return devm_mfd_add_devices(dev, id: -1, cells: atmel_hlcdc_cells, |
131 | ARRAY_SIZE(atmel_hlcdc_cells), |
132 | NULL, irq_base: 0, NULL); |
133 | } |
134 | |
135 | static const struct of_device_id atmel_hlcdc_match[] = { |
136 | { .compatible = "atmel,at91sam9n12-hlcdc" }, |
137 | { .compatible = "atmel,at91sam9x5-hlcdc" }, |
138 | { .compatible = "atmel,sama5d2-hlcdc" }, |
139 | { .compatible = "atmel,sama5d3-hlcdc" }, |
140 | { .compatible = "atmel,sama5d4-hlcdc" }, |
141 | { .compatible = "microchip,sam9x60-hlcdc" }, |
142 | { .compatible = "microchip,sam9x75-xlcdc" }, |
143 | { /* sentinel */ }, |
144 | }; |
145 | MODULE_DEVICE_TABLE(of, atmel_hlcdc_match); |
146 | |
147 | static struct platform_driver atmel_hlcdc_driver = { |
148 | .probe = atmel_hlcdc_probe, |
149 | .driver = { |
150 | .name = "atmel-hlcdc" , |
151 | .of_match_table = atmel_hlcdc_match, |
152 | }, |
153 | }; |
154 | module_platform_driver(atmel_hlcdc_driver); |
155 | |
156 | MODULE_ALIAS("platform:atmel-hlcdc" ); |
157 | MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>" ); |
158 | MODULE_DESCRIPTION("Atmel HLCDC driver" ); |
159 | MODULE_LICENSE("GPL v2" ); |
160 | |