1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Linux driver for RPC-IF HyperFlash |
4 | * |
5 | * Copyright (C) 2019-2020 Cogent Embedded, Inc. |
6 | */ |
7 | |
8 | #include <linux/err.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/mtd/hyperbus.h> |
12 | #include <linux/mtd/mtd.h> |
13 | #include <linux/mux/consumer.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/types.h> |
17 | |
18 | #include <memory/renesas-rpc-if.h> |
19 | |
20 | struct rpcif_hyperbus { |
21 | struct rpcif rpc; |
22 | struct hyperbus_ctlr ctlr; |
23 | struct hyperbus_device hbdev; |
24 | }; |
25 | |
26 | static const struct rpcif_op rpcif_op_tmpl = { |
27 | .cmd = { |
28 | .buswidth = 8, |
29 | .ddr = true, |
30 | }, |
31 | .ocmd = { |
32 | .buswidth = 8, |
33 | .ddr = true, |
34 | }, |
35 | .addr = { |
36 | .nbytes = 1, |
37 | .buswidth = 8, |
38 | .ddr = true, |
39 | }, |
40 | .data = { |
41 | .buswidth = 8, |
42 | .ddr = true, |
43 | }, |
44 | }; |
45 | |
46 | static void rpcif_hb_prepare_read(struct rpcif *rpc, void *to, |
47 | unsigned long from, ssize_t len) |
48 | { |
49 | struct rpcif_op op = rpcif_op_tmpl; |
50 | |
51 | op.cmd.opcode = HYPERBUS_RW_READ | HYPERBUS_AS_MEM; |
52 | op.addr.val = from >> 1; |
53 | op.dummy.buswidth = 1; |
54 | op.dummy.ncycles = 15; |
55 | op.data.dir = RPCIF_DATA_IN; |
56 | op.data.nbytes = len; |
57 | op.data.buf.in = to; |
58 | |
59 | rpcif_prepare(dev: rpc->dev, op: &op, NULL, NULL); |
60 | } |
61 | |
62 | static void rpcif_hb_prepare_write(struct rpcif *rpc, unsigned long to, |
63 | void *from, ssize_t len) |
64 | { |
65 | struct rpcif_op op = rpcif_op_tmpl; |
66 | |
67 | op.cmd.opcode = HYPERBUS_RW_WRITE | HYPERBUS_AS_MEM; |
68 | op.addr.val = to >> 1; |
69 | op.data.dir = RPCIF_DATA_OUT; |
70 | op.data.nbytes = len; |
71 | op.data.buf.out = from; |
72 | |
73 | rpcif_prepare(dev: rpc->dev, op: &op, NULL, NULL); |
74 | } |
75 | |
76 | static u16 rpcif_hb_read16(struct hyperbus_device *hbdev, unsigned long addr) |
77 | { |
78 | struct rpcif_hyperbus *hyperbus = |
79 | container_of(hbdev, struct rpcif_hyperbus, hbdev); |
80 | map_word data; |
81 | |
82 | rpcif_hb_prepare_read(rpc: &hyperbus->rpc, to: &data, from: addr, len: 2); |
83 | |
84 | rpcif_manual_xfer(dev: hyperbus->rpc.dev); |
85 | |
86 | return data.x[0]; |
87 | } |
88 | |
89 | static void rpcif_hb_write16(struct hyperbus_device *hbdev, unsigned long addr, |
90 | u16 data) |
91 | { |
92 | struct rpcif_hyperbus *hyperbus = |
93 | container_of(hbdev, struct rpcif_hyperbus, hbdev); |
94 | |
95 | rpcif_hb_prepare_write(rpc: &hyperbus->rpc, to: addr, from: &data, len: 2); |
96 | |
97 | rpcif_manual_xfer(dev: hyperbus->rpc.dev); |
98 | } |
99 | |
100 | static void rpcif_hb_copy_from(struct hyperbus_device *hbdev, void *to, |
101 | unsigned long from, ssize_t len) |
102 | { |
103 | struct rpcif_hyperbus *hyperbus = |
104 | container_of(hbdev, struct rpcif_hyperbus, hbdev); |
105 | |
106 | rpcif_hb_prepare_read(rpc: &hyperbus->rpc, to, from, len); |
107 | |
108 | rpcif_dirmap_read(dev: hyperbus->rpc.dev, offs: from, len, buf: to); |
109 | } |
110 | |
111 | static const struct hyperbus_ops rpcif_hb_ops = { |
112 | .read16 = rpcif_hb_read16, |
113 | .write16 = rpcif_hb_write16, |
114 | .copy_from = rpcif_hb_copy_from, |
115 | }; |
116 | |
117 | static int rpcif_hb_probe(struct platform_device *pdev) |
118 | { |
119 | struct device *dev = &pdev->dev; |
120 | struct rpcif_hyperbus *hyperbus; |
121 | int error; |
122 | |
123 | hyperbus = devm_kzalloc(dev, size: sizeof(*hyperbus), GFP_KERNEL); |
124 | if (!hyperbus) |
125 | return -ENOMEM; |
126 | |
127 | error = rpcif_sw_init(rpc: &hyperbus->rpc, dev: pdev->dev.parent); |
128 | if (error) |
129 | return error; |
130 | |
131 | platform_set_drvdata(pdev, data: hyperbus); |
132 | |
133 | pm_runtime_enable(dev: hyperbus->rpc.dev); |
134 | |
135 | error = rpcif_hw_init(dev: hyperbus->rpc.dev, hyperflash: true); |
136 | if (error) |
137 | goto out_disable_rpm; |
138 | |
139 | hyperbus->hbdev.map.size = hyperbus->rpc.size; |
140 | hyperbus->hbdev.map.virt = hyperbus->rpc.dirmap; |
141 | |
142 | hyperbus->ctlr.dev = dev; |
143 | hyperbus->ctlr.ops = &rpcif_hb_ops; |
144 | hyperbus->hbdev.ctlr = &hyperbus->ctlr; |
145 | hyperbus->hbdev.np = of_get_next_child(node: pdev->dev.parent->of_node, NULL); |
146 | error = hyperbus_register_device(hbdev: &hyperbus->hbdev); |
147 | if (error) |
148 | goto out_disable_rpm; |
149 | |
150 | return 0; |
151 | |
152 | out_disable_rpm: |
153 | pm_runtime_disable(dev: hyperbus->rpc.dev); |
154 | return error; |
155 | } |
156 | |
157 | static void rpcif_hb_remove(struct platform_device *pdev) |
158 | { |
159 | struct rpcif_hyperbus *hyperbus = platform_get_drvdata(pdev); |
160 | |
161 | hyperbus_unregister_device(hbdev: &hyperbus->hbdev); |
162 | |
163 | pm_runtime_disable(dev: hyperbus->rpc.dev); |
164 | } |
165 | |
166 | static struct platform_driver rpcif_platform_driver = { |
167 | .probe = rpcif_hb_probe, |
168 | .remove_new = rpcif_hb_remove, |
169 | .driver = { |
170 | .name = "rpc-if-hyperflash" , |
171 | }, |
172 | }; |
173 | |
174 | module_platform_driver(rpcif_platform_driver); |
175 | |
176 | MODULE_DESCRIPTION("Renesas RPC-IF HyperFlash driver" ); |
177 | MODULE_LICENSE("GPL v2" ); |
178 | |