| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Realtek DHC pin controller driver |
| * |
| * Copyright (c) 2023 Realtek Semiconductor Corp. |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/pinctrl/machine.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/pinctrl/pinconf-generic.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.h> |
| #include <linux/platform_device.h> |
| #include <linux/seq_file.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include "../core.h" |
| #include "../pinctrl-utils.h" |
| #include "pinctrl-rtd.h" |
| |
| struct rtd_pinctrl { |
| struct device *dev; |
| struct pinctrl_dev *pcdev; |
| void __iomem *base; |
| struct pinctrl_desc desc; |
| const struct rtd_pinctrl_desc *info; |
| struct regmap *regmap_pinctrl; |
| u32 **saved_regs; |
| }; |
| |
| /* custom pinconf parameters */ |
| #define RTD_DRIVE_STRENGH_P (PIN_CONFIG_END + 1) |
| #define RTD_DRIVE_STRENGH_N (PIN_CONFIG_END + 2) |
| #define RTD_DUTY_CYCLE (PIN_CONFIG_END + 3) |
| #define RTD_HIGH_VIL (PIN_CONFIG_END + 4) |
| |
| static const struct pinconf_generic_params rtd_custom_bindings[] = { |
| {"realtek,drive-strength-p", RTD_DRIVE_STRENGH_P, 0}, |
| {"realtek,drive-strength-n", RTD_DRIVE_STRENGH_N, 0}, |
| {"realtek,duty-cycle", RTD_DUTY_CYCLE, 0}, |
| {"realtek,high-vil-microvolt", RTD_HIGH_VIL, 0}, |
| }; |
| |
| static int rtd_pinctrl_get_groups_count(struct pinctrl_dev *pcdev) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| return data->info->num_groups; |
| } |
| |
| static const char *rtd_pinctrl_get_group_name(struct pinctrl_dev *pcdev, |
| unsigned int selector) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| return data->info->groups[selector].name; |
| } |
| |
| static int rtd_pinctrl_get_group_pins(struct pinctrl_dev *pcdev, |
| unsigned int selector, |
| const unsigned int **pins, |
| unsigned int *num_pins) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| *pins = data->info->groups[selector].pins; |
| *num_pins = data->info->groups[selector].num_pins; |
| |
| return 0; |
| } |
| |
| static void rtd_pinctrl_dbg_show(struct pinctrl_dev *pcdev, |
| struct seq_file *s, |
| unsigned int offset) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| const struct rtd_pin_desc *mux = &data->info->muxes[offset]; |
| const struct rtd_pin_mux_desc *func; |
| u32 val; |
| u32 mask; |
| u32 pin_val; |
| int is_map; |
| |
| if (!mux->name) { |
| seq_puts(s, "[not defined]"); |
| return; |
| } |
| val = readl_relaxed(data->base + mux->mux_offset); |
| mask = mux->mux_mask; |
| pin_val = val & mask; |
| |
| is_map = 0; |
| func = &mux->functions[0]; |
| seq_puts(s, "function: "); |
| while (func->name) { |
| if (func->mux_value == pin_val) { |
| is_map = 1; |
| seq_printf(s, "[%s] ", func->name); |
| } else { |
| seq_printf(s, "%s ", func->name); |
| } |
| func++; |
| } |
| if (!is_map) |
| seq_puts(s, "[not defined]"); |
| } |
| |
| static const struct pinctrl_ops rtd_pinctrl_ops = { |
| .dt_node_to_map = pinconf_generic_dt_node_to_map_all, |
| .dt_free_map = pinctrl_utils_free_map, |
| .get_groups_count = rtd_pinctrl_get_groups_count, |
| .get_group_name = rtd_pinctrl_get_group_name, |
| .get_group_pins = rtd_pinctrl_get_group_pins, |
| .pin_dbg_show = rtd_pinctrl_dbg_show, |
| }; |
| |
| static int rtd_pinctrl_get_functions_count(struct pinctrl_dev *pcdev) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| return data->info->num_functions; |
| } |
| |
| static const char *rtd_pinctrl_get_function_name(struct pinctrl_dev *pcdev, |
| unsigned int selector) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| return data->info->functions[selector].name; |
| } |
| |
| static int rtd_pinctrl_get_function_groups(struct pinctrl_dev *pcdev, |
| unsigned int selector, |
| const char * const **groups, |
| unsigned int * const num_groups) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| |
| *groups = data->info->functions[selector].groups; |
| *num_groups = data->info->functions[selector].num_groups; |
| |
| return 0; |
| } |
| |
| static const struct rtd_pin_desc *rtd_pinctrl_find_mux(struct rtd_pinctrl *data, unsigned int pin) |
| { |
| if (data->info->muxes[pin].name) |
| return &data->info->muxes[pin]; |
| |
| return NULL; |
| } |
| |
| static int rtd_pinctrl_set_one_mux(struct pinctrl_dev *pcdev, |
| unsigned int pin, const char *func_name) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| const struct rtd_pin_desc *mux; |
| int ret = 0; |
| int i; |
| |
| mux = rtd_pinctrl_find_mux(data, pin); |
| if (!mux) |
| return 0; |
| |
| if (!mux->functions) { |
| if (!mux->name) |
| dev_err(pcdev->dev, "NULL pin has no functions\n"); |
| else |
| dev_err(pcdev->dev, "No functions available for pin %s\n", mux->name); |
| return -ENOTSUPP; |
| } |
| |
| for (i = 0; mux->functions[i].name; i++) { |
| if (strcmp(mux->functions[i].name, func_name) != 0) |
| continue; |
| ret = regmap_update_bits(data->regmap_pinctrl, mux->mux_offset, mux->mux_mask, |
| mux->functions[i].mux_value); |
| return ret; |
| } |
| |
| if (!mux->name) { |
| dev_err(pcdev->dev, "NULL pin provided for function %s\n", func_name); |
| return -EINVAL; |
| } |
| |
| dev_err(pcdev->dev, "No function %s available for pin %s\n", func_name, mux->name); |
| |
| return -EINVAL; |
| } |
| |
| static int rtd_pinctrl_set_mux(struct pinctrl_dev *pcdev, |
| unsigned int function, unsigned int group) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| const unsigned int *pins; |
| unsigned int num_pins; |
| const char *func_name; |
| const char *group_name; |
| int i, ret; |
| |
| func_name = data->info->functions[function].name; |
| group_name = data->info->groups[group].name; |
| |
| ret = rtd_pinctrl_get_group_pins(pcdev, group, &pins, &num_pins); |
| if (ret) { |
| dev_err(pcdev->dev, "Getting pins for group %s failed\n", group_name); |
| return ret; |
| } |
| |
| for (i = 0; i < num_pins; i++) { |
| ret = rtd_pinctrl_set_one_mux(pcdev, pins[i], func_name); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int rtd_pinctrl_gpio_request_enable(struct pinctrl_dev *pcdev, |
| struct pinctrl_gpio_range *range, |
| unsigned int offset) |
| { |
| return rtd_pinctrl_set_one_mux(pcdev, offset, "gpio"); |
| } |
| |
| static const struct pinmux_ops rtd_pinmux_ops = { |
| .get_functions_count = rtd_pinctrl_get_functions_count, |
| .get_function_name = rtd_pinctrl_get_function_name, |
| .get_function_groups = rtd_pinctrl_get_function_groups, |
| .set_mux = rtd_pinctrl_set_mux, |
| .gpio_request_enable = rtd_pinctrl_gpio_request_enable, |
| }; |
| |
| static const struct pinctrl_pin_desc |
| *rtd_pinctrl_get_pin_by_number(struct rtd_pinctrl *data, int number) |
| { |
| int i; |
| |
| for (i = 0; i < data->info->num_pins; i++) { |
| if (data->info->pins[i].number == number) |
| return &data->info->pins[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static const struct rtd_pin_config_desc |
| *rtd_pinctrl_find_config(struct rtd_pinctrl *data, unsigned int pin) |
| { |
| if (data->info->configs[pin].name) |
| return &data->info->configs[pin]; |
| |
| return NULL; |
| } |
| |
| static const struct rtd_pin_sconfig_desc *rtd_pinctrl_find_sconfig(struct rtd_pinctrl *data, |
| unsigned int pin) |
| { |
| int i; |
| const struct pinctrl_pin_desc *pin_desc; |
| const char *pin_name; |
| |
| pin_desc = rtd_pinctrl_get_pin_by_number(data, pin); |
| if (!pin_desc) |
| return NULL; |
| |
| pin_name = pin_desc->name; |
| |
| for (i = 0; i < data->info->num_sconfigs; i++) { |
| if (strcmp(data->info->sconfigs[i].name, pin_name) == 0) |
| return &data->info->sconfigs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static int rtd_pconf_parse_conf(struct rtd_pinctrl *data, |
| unsigned int pinnr, |
| enum pin_config_param param, |
| unsigned int arg) |
| { |
| const struct rtd_pin_config_desc *config_desc; |
| const struct rtd_pin_sconfig_desc *sconfig_desc; |
| u8 set_val = 0; |
| u16 strength; |
| u32 val; |
| u32 mask; |
| u32 pulsel_off, pulen_off, smt_off, curr_off, pow_off, reg_off, p_off, n_off, |
| input_volt_off, sr_off, hvil_off; |
| const char *name = data->info->pins[pinnr].name; |
| int ret = 0; |
| |
| config_desc = rtd_pinctrl_find_config(data, pinnr); |
| if (!config_desc) { |
| dev_err(data->dev, "Pin config unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| switch ((u32)param) { |
| case PIN_CONFIG_INPUT_SCHMITT: |
| case PIN_CONFIG_INPUT_SCHMITT_ENABLE: |
| if (config_desc->smt_offset == NA) { |
| dev_err(data->dev, "Input schmitt unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| smt_off = config_desc->base_bit + config_desc->smt_offset; |
| reg_off = config_desc->reg_offset; |
| set_val = arg; |
| |
| mask = BIT(smt_off); |
| val = set_val ? BIT(smt_off) : 0; |
| break; |
| |
| case PIN_CONFIG_DRIVE_PUSH_PULL: |
| if (config_desc->pud_en_offset == NA) { |
| dev_err(data->dev, "Push pull unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| pulen_off = config_desc->base_bit + config_desc->pud_en_offset; |
| reg_off = config_desc->reg_offset; |
| |
| mask = BIT(pulen_off); |
| val = 0; |
| break; |
| |
| case PIN_CONFIG_BIAS_DISABLE: |
| if (config_desc->pud_en_offset == NA) { |
| dev_err(data->dev, "Bias disable unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| pulen_off = config_desc->base_bit + config_desc->pud_en_offset; |
| reg_off = config_desc->reg_offset; |
| |
| mask = BIT(pulen_off); |
| val = 0; |
| break; |
| |
| case PIN_CONFIG_BIAS_PULL_UP: |
| if (config_desc->pud_en_offset == NA) { |
| dev_err(data->dev, "Bias pull up unsupported for pin:%s\n", name); |
| return -ENOTSUPP; |
| } |
| pulen_off = config_desc->base_bit + config_desc->pud_en_offset; |
| pulsel_off = config_desc->base_bit + config_desc->pud_sel_offset; |
| reg_off = config_desc->reg_offset; |
| |
| mask = BIT(pulen_off) | BIT(pulsel_off); |
| val = mask; |
| break; |
| |
| case PIN_CONFIG_BIAS_PULL_DOWN: |
| if (config_desc->pud_en_offset == NA) { |
| dev_err(data->dev, "Bias pull down unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| pulen_off = config_desc->base_bit + config_desc->pud_en_offset; |
| pulsel_off = config_desc->base_bit + config_desc->pud_sel_offset; |
| reg_off = config_desc->reg_offset; |
| |
| mask = BIT(pulen_off) | BIT(pulsel_off); |
| val = BIT(pulen_off); |
| break; |
| |
| case PIN_CONFIG_DRIVE_STRENGTH: |
| curr_off = config_desc->base_bit + config_desc->curr_offset; |
| reg_off = config_desc->reg_offset; |
| strength = arg; |
| val = 0; |
| switch (config_desc->curr_type) { |
| case PADDRI_4_8: |
| if (strength == 4) |
| val = 0; |
| else if (strength == 8) |
| val = BIT(curr_off); |
| else |
| return -EINVAL; |
| break; |
| case PADDRI_2_4: |
| if (strength == 2) |
| val = 0; |
| else if (strength == 4) |
| val = BIT(curr_off); |
| else |
| return -EINVAL; |
| break; |
| case NA: |
| dev_err(data->dev, "Drive strength unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| default: |
| return -EINVAL; |
| } |
| mask = BIT(curr_off); |
| break; |
| |
| case PIN_CONFIG_POWER_SOURCE: |
| if (config_desc->power_offset == NA) { |
| dev_err(data->dev, "Power source unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| reg_off = config_desc->reg_offset; |
| pow_off = config_desc->base_bit + config_desc->power_offset; |
| if (pow_off >= 32) { |
| reg_off += 0x4; |
| pow_off -= 32; |
| } |
| set_val = arg; |
| mask = BIT(pow_off); |
| val = set_val ? mask : 0; |
| break; |
| |
| case PIN_CONFIG_SLEW_RATE: |
| if (config_desc->slew_rate_offset == NA) { |
| dev_err(data->dev, "Slew rate setting unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| |
| switch (arg) { |
| case 1: |
| set_val = 0; |
| break; |
| case 10: |
| set_val = 1; |
| break; |
| case 20: |
| set_val = 2; |
| break; |
| case 30: |
| set_val = 3; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| sr_off = config_desc->base_bit + config_desc->slew_rate_offset; |
| reg_off = config_desc->reg_offset; |
| mask = 0x3 << sr_off; |
| val = arg << sr_off; |
| break; |
| |
| case PIN_CONFIG_INPUT_VOLTAGE_UV: |
| if (config_desc->input_volt_offset == NA) { |
| dev_err(data->dev, "Input voltage level setting unsupported for pin:%s\n", |
| name); |
| return -ENOTSUPP; |
| } |
| |
| if (arg == 3300000) |
| set_val = 1; |
| else if (arg == 1800000) |
| set_val = 0; |
| else |
| return -EINVAL; |
| |
| input_volt_off = config_desc->base_bit + config_desc->input_volt_offset; |
| reg_off = config_desc->reg_offset; |
| |
| mask = BIT(input_volt_off); |
| val = set_val ? BIT(input_volt_off) : 0; |
| break; |
| |
| case RTD_HIGH_VIL: |
| if (config_desc->hvil_offset == NA) { |
| dev_err(data->dev, "High vil setting unsupported for pin:%s\n", name); |
| return -ENOTSUPP; |
| } |
| hvil_off = config_desc->base_bit + config_desc->hvil_offset; |
| reg_off = config_desc->reg_offset; |
| mask = BIT(hvil_off); |
| val = 1; |
| break; |
| |
| case RTD_DRIVE_STRENGH_P: |
| sconfig_desc = rtd_pinctrl_find_sconfig(data, pinnr); |
| if (!sconfig_desc) { |
| dev_err(data->dev, "P driving unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| set_val = arg; |
| reg_off = sconfig_desc->reg_offset; |
| p_off = sconfig_desc->pdrive_offset; |
| if (p_off >= 32) { |
| reg_off += 0x4; |
| p_off -= 32; |
| } |
| mask = GENMASK(p_off + sconfig_desc->pdrive_maskbits - 1, p_off); |
| val = set_val << p_off; |
| break; |
| |
| case RTD_DRIVE_STRENGH_N: |
| sconfig_desc = rtd_pinctrl_find_sconfig(data, pinnr); |
| if (!sconfig_desc) { |
| dev_err(data->dev, "N driving unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| set_val = arg; |
| reg_off = sconfig_desc->reg_offset; |
| n_off = sconfig_desc->ndrive_offset; |
| if (n_off >= 32) { |
| reg_off += 0x4; |
| n_off -= 32; |
| } |
| mask = GENMASK(n_off + sconfig_desc->ndrive_maskbits - 1, n_off); |
| val = set_val << n_off; |
| break; |
| |
| case RTD_DUTY_CYCLE: |
| sconfig_desc = rtd_pinctrl_find_sconfig(data, pinnr); |
| if (!sconfig_desc || sconfig_desc->dcycle_offset == NA) { |
| dev_err(data->dev, "Duty cycle unsupported for pin: %s\n", name); |
| return -ENOTSUPP; |
| } |
| set_val = arg; |
| reg_off = config_desc->reg_offset; |
| mask = GENMASK(sconfig_desc->dcycle_offset + |
| sconfig_desc->dcycle_maskbits - 1, sconfig_desc->dcycle_offset); |
| val = set_val << sconfig_desc->dcycle_offset; |
| break; |
| |
| default: |
| dev_dbg(data->dev, "unsupported pinconf: %d\n", (u32)param); |
| return -ENOTSUPP; |
| } |
| |
| ret = regmap_update_bits(data->regmap_pinctrl, reg_off, mask, val); |
| if (ret) |
| dev_err(data->dev, "could not update pinconf(%d) for pin(%s)\n", (u32)param, name); |
| |
| return ret; |
| } |
| |
| static int rtd_pin_config_get(struct pinctrl_dev *pcdev, unsigned int pinnr, |
| unsigned long *config) |
| { |
| unsigned int param = pinconf_to_config_param(*config); |
| unsigned int arg = 0; |
| |
| switch (param) { |
| default: |
| return -ENOTSUPP; |
| } |
| |
| *config = pinconf_to_config_packed(param, arg); |
| return 0; |
| } |
| |
| static int rtd_pin_config_set(struct pinctrl_dev *pcdev, unsigned int pinnr, |
| unsigned long *configs, unsigned int num_configs) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| int i; |
| int ret = 0; |
| |
| for (i = 0; i < num_configs; i++) { |
| ret = rtd_pconf_parse_conf(data, pinnr, |
| pinconf_to_config_param(configs[i]), |
| pinconf_to_config_argument(configs[i])); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int rtd_pin_config_group_set(struct pinctrl_dev *pcdev, unsigned int group, |
| unsigned long *configs, unsigned int num_configs) |
| { |
| struct rtd_pinctrl *data = pinctrl_dev_get_drvdata(pcdev); |
| const unsigned int *pins; |
| unsigned int num_pins; |
| const char *group_name; |
| int i, ret; |
| |
| group_name = data->info->groups[group].name; |
| |
| ret = rtd_pinctrl_get_group_pins(pcdev, group, &pins, &num_pins); |
| if (ret) { |
| dev_err(pcdev->dev, "Getting pins for group %s failed\n", group_name); |
| return ret; |
| } |
| |
| for (i = 0; i < num_pins; i++) { |
| ret = rtd_pin_config_set(pcdev, pins[i], configs, num_configs); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct pinconf_ops rtd_pinconf_ops = { |
| .is_generic = true, |
| .pin_config_get = rtd_pin_config_get, |
| .pin_config_set = rtd_pin_config_set, |
| .pin_config_group_set = rtd_pin_config_group_set, |
| }; |
| |
| static const struct regmap_config rtd_pinctrl_regmap_config = { |
| .reg_bits = 32, |
| .val_bits = 32, |
| .reg_stride = 4, |
| .use_relaxed_mmio = true, |
| }; |
| |
| static int rtd_pinctrl_init_pm(struct rtd_pinctrl *data) |
| { |
| const struct rtd_pin_range *pin_range = data->info->pin_range; |
| struct device *dev = data->pcdev->dev; |
| const struct rtd_reg_range *range; |
| size_t num_entries; |
| int i; |
| |
| data->saved_regs = devm_kcalloc(dev, pin_range->num_ranges, sizeof(u32 *), GFP_KERNEL); |
| if (!data->saved_regs) |
| return -ENOMEM; |
| |
| for (i = 0; i < pin_range->num_ranges; i++) { |
| range = &pin_range->ranges[i]; |
| num_entries = range->len / 4; |
| |
| data->saved_regs[i] = devm_kzalloc(dev, num_entries * sizeof(u32), GFP_KERNEL); |
| if (!data->saved_regs[i]) |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| int rtd_pinctrl_probe(struct platform_device *pdev, const struct rtd_pinctrl_desc *desc) |
| { |
| struct rtd_pinctrl *data; |
| |
| data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| data->base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(data->base)) |
| return dev_err_probe(&pdev->dev, PTR_ERR(data->base), |
| "Failed to ioremap resource\n"); |
| |
| data->dev = &pdev->dev; |
| data->info = desc; |
| data->desc.name = dev_name(&pdev->dev); |
| data->desc.pins = data->info->pins; |
| data->desc.npins = data->info->num_pins; |
| data->desc.pctlops = &rtd_pinctrl_ops; |
| data->desc.pmxops = &rtd_pinmux_ops; |
| data->desc.confops = &rtd_pinconf_ops; |
| data->desc.custom_params = rtd_custom_bindings; |
| data->desc.num_custom_params = ARRAY_SIZE(rtd_custom_bindings); |
| data->desc.owner = THIS_MODULE; |
| data->regmap_pinctrl = devm_regmap_init_mmio(data->dev, data->base, |
| &rtd_pinctrl_regmap_config); |
| |
| if (IS_ERR(data->regmap_pinctrl)) |
| return dev_err_probe(data->dev, PTR_ERR(data->regmap_pinctrl), |
| "Failed to init regmap\n"); |
| |
| data->pcdev = devm_pinctrl_register(&pdev->dev, &data->desc, data); |
| if (IS_ERR(data->pcdev)) |
| return dev_err_probe(data->dev, PTR_ERR(data->pcdev), |
| "Failed to register pinctrl\n"); |
| |
| platform_set_drvdata(pdev, data); |
| |
| dev_dbg(&pdev->dev, "probed\n"); |
| |
| if (data->info->pin_range) { |
| if (rtd_pinctrl_init_pm(data)) |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(rtd_pinctrl_probe); |
| |
| static int realtek_pinctrl_suspend(struct device *dev) |
| { |
| struct rtd_pinctrl *data = dev_get_drvdata(dev); |
| const struct rtd_pin_range *pin_range = data->info->pin_range; |
| const struct rtd_reg_range *range; |
| u32 *range_regs; |
| int count; |
| int i, j; |
| int ret; |
| |
| if (!data->saved_regs) |
| return 0; |
| |
| for (i = 0; i < pin_range->num_ranges; i++) { |
| range = &pin_range->ranges[i]; |
| range_regs = data->saved_regs[i]; |
| count = range->len / 4; |
| |
| for (j = 0; j < count; j++) { |
| ret = regmap_read(data->regmap_pinctrl, range->offset + (j * 4), |
| &range_regs[j]); |
| if (ret) { |
| dev_err(dev, "failed to store register 0x%x: %d\n", |
| range->offset + (j * 4), ret); |
| return ret; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int realtek_pinctrl_resume(struct device *dev) |
| { |
| struct rtd_pinctrl *data = dev_get_drvdata(dev); |
| const struct rtd_pin_range *pin_range = data->info->pin_range; |
| const struct rtd_reg_range *range; |
| u32 *range_regs; |
| int count; |
| int i, j; |
| int ret; |
| |
| if (!data->saved_regs) |
| return 0; |
| |
| for (i = 0; i < pin_range->num_ranges; i++) { |
| range = &pin_range->ranges[i]; |
| range_regs = data->saved_regs[i]; |
| count = range->len / 4; |
| |
| for (j = 0; j < count; j++) { |
| ret = regmap_write(data->regmap_pinctrl, range->offset + (j * 4), |
| range_regs[j]); |
| if (ret) { |
| dev_err(dev, "failed to restore register 0x%x: %d\n", |
| range->offset + (j * 4), ret); |
| return ret; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| const struct dev_pm_ops realtek_pinctrl_pm_ops = { |
| NOIRQ_SYSTEM_SLEEP_PM_OPS(realtek_pinctrl_suspend, realtek_pinctrl_resume) |
| }; |
| EXPORT_SYMBOL_GPL(realtek_pinctrl_pm_ops); |
| |
| MODULE_DESCRIPTION("Realtek DHC SoC pinctrl driver"); |
| MODULE_LICENSE("GPL"); |