1 | // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
2 | /* |
3 | * Second generation of pinmux driver for Amlogic Meson-AXG SoC. |
4 | * |
5 | * Copyright (c) 2017 Baylibre SAS. |
6 | * Author: Jerome Brunet <jbrunet@baylibre.com> |
7 | * |
8 | * Copyright (c) 2017 Amlogic, Inc. All rights reserved. |
9 | * Author: Xingyu Chen <xingyu.chen@amlogic.com> |
10 | */ |
11 | |
12 | /* |
13 | * This new generation of pinctrl IP is mainly adopted by the |
14 | * Meson-AXG SoC and later series, which use 4-width continuous |
15 | * register bit to select the function for each pin. |
16 | * |
17 | * The value 0 is always selecting the GPIO mode, while other |
18 | * values (start from 1) for selecting the function mode. |
19 | */ |
20 | #include <linux/device.h> |
21 | #include <linux/regmap.h> |
22 | #include <linux/pinctrl/pinctrl.h> |
23 | #include <linux/pinctrl/pinmux.h> |
24 | |
25 | #include "pinctrl-meson.h" |
26 | #include "pinctrl-meson-axg-pmx.h" |
27 | |
28 | static int meson_axg_pmx_get_bank(struct meson_pinctrl *pc, |
29 | unsigned int pin, |
30 | struct meson_pmx_bank **bank) |
31 | { |
32 | int i; |
33 | struct meson_axg_pmx_data *pmx = pc->data->pmx_data; |
34 | |
35 | for (i = 0; i < pmx->num_pmx_banks; i++) |
36 | if (pin >= pmx->pmx_banks[i].first && |
37 | pin <= pmx->pmx_banks[i].last) { |
38 | *bank = &pmx->pmx_banks[i]; |
39 | return 0; |
40 | } |
41 | |
42 | return -EINVAL; |
43 | } |
44 | |
45 | static int meson_pmx_calc_reg_and_offset(struct meson_pmx_bank *bank, |
46 | unsigned int pin, unsigned int *reg, |
47 | unsigned int *offset) |
48 | { |
49 | int shift; |
50 | |
51 | shift = pin - bank->first; |
52 | |
53 | *reg = bank->reg + (bank->offset + (shift << 2)) / 32; |
54 | *offset = (bank->offset + (shift << 2)) % 32; |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int meson_axg_pmx_update_function(struct meson_pinctrl *pc, |
60 | unsigned int pin, unsigned int func) |
61 | { |
62 | int ret; |
63 | int reg; |
64 | int offset; |
65 | struct meson_pmx_bank *bank; |
66 | |
67 | ret = meson_axg_pmx_get_bank(pc, pin, bank: &bank); |
68 | if (ret) |
69 | return ret; |
70 | |
71 | meson_pmx_calc_reg_and_offset(bank, pin, reg: ®, offset: &offset); |
72 | |
73 | ret = regmap_update_bits(map: pc->reg_mux, reg: reg << 2, |
74 | mask: 0xf << offset, val: (func & 0xf) << offset); |
75 | |
76 | return ret; |
77 | } |
78 | |
79 | static int meson_axg_pmx_set_mux(struct pinctrl_dev *pcdev, |
80 | unsigned int func_num, unsigned int group_num) |
81 | { |
82 | int i; |
83 | int ret; |
84 | struct meson_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev: pcdev); |
85 | struct meson_pmx_func *func = &pc->data->funcs[func_num]; |
86 | struct meson_pmx_group *group = &pc->data->groups[group_num]; |
87 | struct meson_pmx_axg_data *pmx_data = |
88 | (struct meson_pmx_axg_data *)group->data; |
89 | |
90 | dev_dbg(pc->dev, "enable function %s, group %s\n" , func->name, |
91 | group->name); |
92 | |
93 | for (i = 0; i < group->num_pins; i++) { |
94 | ret = meson_axg_pmx_update_function(pc, pin: group->pins[i], |
95 | func: pmx_data->func); |
96 | if (ret) |
97 | return ret; |
98 | } |
99 | |
100 | return 0; |
101 | } |
102 | |
103 | static int meson_axg_pmx_request_gpio(struct pinctrl_dev *pcdev, |
104 | struct pinctrl_gpio_range *range, unsigned int offset) |
105 | { |
106 | struct meson_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev: pcdev); |
107 | |
108 | return meson_axg_pmx_update_function(pc, pin: offset, func: 0); |
109 | } |
110 | |
111 | const struct pinmux_ops meson_axg_pmx_ops = { |
112 | .set_mux = meson_axg_pmx_set_mux, |
113 | .get_functions_count = meson_pmx_get_funcs_count, |
114 | .get_function_name = meson_pmx_get_func_name, |
115 | .get_function_groups = meson_pmx_get_groups, |
116 | .gpio_request_enable = meson_axg_pmx_request_gpio, |
117 | }; |
118 | EXPORT_SYMBOL_GPL(meson_axg_pmx_ops); |
119 | |
120 | MODULE_LICENSE("Dual BSD/GPL" ); |
121 | |