/* * Extended support for Cirrus Logic Smart Amplifiers * * Copyright 2017 Cirrus Logic * * Author: David Rhodes * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include "wmfw.h" #include "wm_adsp.h" struct class *cirrus_amp_class; EXPORT_SYMBOL_GPL(cirrus_amp_class); struct cirrus_amp_group *amp_group; static struct cirrus_cal_ops *cirrus_cal_ops[3] = { &cirrus_cspl_cal_ops, &cirrus_cspl_cal_ops, &cirrus_cs35l43_cal_ops }; struct cirrus_amp *cirrus_get_amp_from_suffix(const char *suffix) { int i; if (amp_group == NULL || (amp_group->num_amps == 0)) return NULL; pr_debug("%s: suffix = %s\n", __func__, suffix); for (i = 0; i < amp_group->num_amps; i++) { if (amp_group->amps[i].bd.bd_suffix) { pr_debug("%s: comparing %s & %s\n", __func__, amp_group->amps[i].bd.bd_suffix, suffix); if (!strcmp(amp_group->amps[i].bd.bd_suffix, suffix)) return &_group->amps[i]; } } for (i = 0; i < amp_group->num_amps; i++) { pr_debug("%s: comparing %s & %s\n", __func__, amp_group->amps[i].mfd_suffix, suffix); if (!strcmp(amp_group->amps[i].mfd_suffix, suffix)) return &_group->amps[i]; } return NULL; } EXPORT_SYMBOL_GPL(cirrus_get_amp_from_suffix); void cirrus_amp_register_i2c_error_callback(const char *suffix, void *func) { struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) amp->i2c_callback = func; } EXPORT_SYMBOL_GPL(cirrus_amp_register_i2c_error_callback); void cirrus_amp_register_error_callback(const char *suffix, void *func) { struct cirrus_amp *amp = cirrus_get_amp_from_suffix(suffix); if (amp) amp->error_callback = func; } EXPORT_SYMBOL_GPL(cirrus_amp_register_error_callback); int cirrus_amp_add(const char *mfd_suffix, struct cirrus_amp_config cfg) { struct cirrus_amp *amp = cirrus_get_amp_from_suffix(mfd_suffix); if (amp) { dev_info(amp_group->cal_dev, "Amp added, suffix: %s dsp_part_name: %s\n", mfd_suffix, cfg.dsp_part_name); amp->component = cfg.component; amp->regmap = cfg.regmap; amp->dsp_part_name = cfg.dsp_part_name; amp->num_pre_configs = cfg.num_pre_configs; amp->num_post_configs = cfg.num_post_configs; amp->mbox_cmd = cfg.mbox_cmd; amp->mbox_sts = cfg.mbox_sts; amp->global_en = cfg.global_en; amp->global_en_mask = cfg.global_en_mask; amp->vimon_alg_id = cfg.vimon_alg_id; amp->pwr.target_temp = cfg.target_temp; amp->pwr.exit_temp = cfg.exit_temp; amp->perform_vimon_cal = cfg.perform_vimon_cal; amp->calibration_disable = cfg.calibration_disable; amp->cal_vpk_id = cfg.cal_vpk_id ? cfg.cal_vpk_id : CIRRUS_CAL_RTLOG_ID_V_PEAK; amp->cal_ipk_id = cfg.cal_ipk_id ? cfg.cal_ipk_id : CIRRUS_CAL_RTLOG_ID_I_PEAK; amp->halo_alg_id = cfg.halo_alg_id ? cfg.halo_alg_id : CIRRUS_AMP_ALG_ID_HALO; amp->bd.bd_alg_id = cfg.bd_alg_id ? cfg.bd_alg_id : CIRRUS_AMP_ALG_ID_CSPL; amp->bd.bd_prefix = cfg.bd_prefix ? cfg.bd_prefix : "BDLOG_"; amp->cal_vsc_ub = cfg.cal_vsc_ub ? cfg.cal_vsc_ub : CIRRUS_CAL_VIMON_CAL_VSC_UB; amp->cal_vsc_lb = cfg.cal_vsc_lb ? cfg.cal_vsc_lb : CIRRUS_CAL_VIMON_CAL_VSC_LB; amp->cal_isc_ub = cfg.cal_isc_ub ? cfg.cal_isc_ub : CIRRUS_CAL_VIMON_CAL_ISC_UB; amp->cal_isc_lb = cfg.cal_isc_lb ? cfg.cal_isc_lb : CIRRUS_CAL_VIMON_CAL_ISC_LB; amp->bd.max_temp_limit = cfg.bd_max_temp ? cfg.bd_max_temp - 1 : 99; amp->default_redc = cfg.default_redc ? cfg.default_redc : CIRRUS_CAL_RDC_DEFAULT; amp->cal_ops = cfg.cal_ops_idx ? cirrus_cal_ops[cfg.cal_ops_idx] : cirrus_cal_ops[CIRRUS_CAL_CSPL_CAL_OPS_IDX]; amp->pre_config = kcalloc(cfg.num_pre_configs, sizeof(struct reg_sequence), GFP_KERNEL); amp->post_config = kcalloc(cfg.num_post_configs, sizeof(struct reg_sequence), GFP_KERNEL); amp->amp_reinit = cfg.amp_reinit; amp->runtime_pm = cfg.runtime_pm; amp_group->pwr_enable |= cfg.pwr_enable; memcpy(amp->pre_config, cfg.pre_config, sizeof(struct reg_sequence) * cfg.num_pre_configs); memcpy(amp->post_config, cfg.post_config, sizeof(struct reg_sequence) * cfg.num_post_configs); } else { dev_err(amp_group->cal_dev, "No amp with suffix %s registered\n", mfd_suffix); return -EINVAL; } return 0; } EXPORT_SYMBOL_GPL(cirrus_amp_add); int cirrus_amp_read_ctl(struct cirrus_amp *amp, const char *name, int type, unsigned int id, unsigned int *value) { struct wm_adsp *dsp; unsigned int tmp; int ret = 0; if (amp && amp->component) dsp = snd_soc_component_get_drvdata(amp->component); else return -EINVAL; if (dsp) { ret = wm_adsp_read_ctl(dsp, name, type, id, (void *)&tmp, 4); *value = (tmp & 0xff0000) >> 8 | (tmp & 0xff00) << 8 | (tmp & 0xff000000) >> 24; } return ret; } EXPORT_SYMBOL_GPL(cirrus_amp_read_ctl); int cirrus_amp_write_ctl(struct cirrus_amp *amp, const char *name, int type, unsigned int id, unsigned int value) { struct wm_adsp *dsp; unsigned int tmp; if (amp && amp->component) dsp = snd_soc_component_get_drvdata(amp->component); else return -EINVAL; tmp = (value & 0xff0000) >> 8 | (value & 0xff00) << 8 | (value & 0xff000000) >> 24 | (value & 0xff) << 24; if (dsp) return wm_adsp_write_ctl(dsp, name, type, id, (void *)&tmp, 4); return 0; } EXPORT_SYMBOL_GPL(cirrus_amp_write_ctl); static const struct of_device_id cirrus_amp_of_match[] = { { .compatible = "cirrus-amp", }, {}, }; MODULE_DEVICE_TABLE(of, cirrus_amp_of_match); static int cirrus_amp_probe(struct platform_device *pdev) { struct device_node *np; struct device_node *amp_node; const char **mfd_suffixes; const char **bd_suffixes; bool v_val_separate[CIRRUS_MAX_AMPS]; int ret = 0, num = 0, num_amps, i, j; cirrus_amp_class = class_create(THIS_MODULE, "cirrus"); if (IS_ERR(cirrus_amp_class)) { ret = PTR_ERR(cirrus_amp_class); pr_err("%s: Unable to register cirrus_amp class (%d)", __func__, ret); return ret; } for_each_matching_node(np, cirrus_amp_of_match) num++; if (num != 1) { pr_info("%s: Exactly 1 OF entry is allowed (%d detected)\n", __func__, num); ret = -EINVAL; goto class_err; } np = of_find_matching_node(NULL, cirrus_amp_of_match); if (!np) { pr_err("%s: Device node required\n", __func__); ret = -ENODEV; goto class_err; } num_amps = of_count_phandle_with_args(np, "cirrus,amps", NULL); if (num_amps <= 0) { pr_err("%s: Failed to parse 'cirrus,amps'\n", __func__); ret = -ENODEV; goto class_err; } amp_group = kzalloc(sizeof(struct cirrus_amp_group) + sizeof(struct cirrus_amp) * num_amps, GFP_KERNEL); mfd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL); bd_suffixes = kcalloc(num_amps, sizeof(char *), GFP_KERNEL); amp_group->num_amps = num_amps; for (i = 0; i < num_amps; i++) { amp_node = of_parse_phandle(np, "cirrus,amps", i); if (IS_ERR(amp_node)) { pr_err("%s: Failed to parse 'cirrus,amps' (%d)\n", __func__, i); ret = PTR_ERR(amp_node); goto suffix_free; } pr_debug("%s: Found linked amp: %s\n", __func__, amp_node->full_name); ret = of_property_read_string(amp_node, "cirrus,mfd-suffix", &mfd_suffixes[i]); if (ret < 0) { pr_err("%s: No MFD suffix found for amp: %s\n", __func__, amp_node->full_name); of_node_put(amp_node); goto suffix_free; } ret = of_property_read_string(amp_node, "cirrus,bd-suffix", &bd_suffixes[i]); if (ret < 0) pr_debug("%s: No BD suffix found for amp: %s\n", __func__, amp_node->full_name); v_val_separate[i] = of_property_read_bool(amp_node, "cirrus,v-val_separate"); of_node_put(amp_node); } for (i = 0; i < num_amps; i++) { for (j = 0; j < num_amps; j++) { if (i == j) continue; if (strcmp(mfd_suffixes[i], mfd_suffixes[j]) == 0) { pr_err("%s: MFD suffixes must be unique\n", __func__); pr_err("%s: Found duplicate suffix: %s\n", __func__, mfd_suffixes[i]); ret = -EINVAL; goto suffix_err; } /* bd_suffixes can be empty but must be unique */ if (bd_suffixes[i] && bd_suffixes[j] && (strcmp(bd_suffixes[i], bd_suffixes[j]) == 0)) { pr_err("%s: BD suffixes must be unique\n", __func__); pr_err("%s: Found duplicate suffix: %s\n", __func__, bd_suffixes[i]); ret = -EINVAL; goto suffix_err; } } pr_info("%s: Found MFD suffix: %s\n", __func__, mfd_suffixes[i]); amp_group->amps[i].mfd_suffix = kstrdup(mfd_suffixes[i], GFP_KERNEL); if (bd_suffixes[i]) { pr_info("%s: Found BD suffix: %s\n", __func__, bd_suffixes[i]); amp_group->amps[i].bd.bd_suffix = kstrdup(bd_suffixes[i], GFP_KERNEL); } if (v_val_separate[i]) amp_group->amps[i].v_val_separate = true; } ret = cirrus_bd_init(); if (ret < 0) { pr_err("%s: Error in BD init (%d)\n", __func__, ret); goto suffix_err; } ret = cirrus_cal_init(); if (ret < 0) { pr_err("%s: Error in CAL init (%d)\n", __func__, ret); cirrus_bd_exit(); goto suffix_err; } ret = cirrus_pwr_init(); if (ret < 0) { pr_err("%s: Error in PWR init (%d)\n", __func__, ret); cirrus_bd_exit(); cirrus_cal_exit(); goto suffix_err; } ret = 0; goto suffix_free; suffix_err: for (i = 0; i < num_amps; i++) { kfree(amp_group->amps[i].mfd_suffix); kfree(amp_group->amps[i].bd.bd_suffix); } suffix_free: kfree(mfd_suffixes); kfree(bd_suffixes); if (ret < 0) kfree(amp_group); class_err: if (ret < 0) class_destroy(cirrus_amp_class); return ret; } static int cirrus_amp_remove(struct platform_device *pdev) { int i; cirrus_cal_exit(); cirrus_bd_exit(); cirrus_pwr_exit(); for (i = 0; i < amp_group->num_amps; i++) { kfree(amp_group->amps[i].pre_config); kfree(amp_group->amps[i].post_config); } kfree(amp_group); class_destroy(cirrus_amp_class); return 0; } static struct platform_driver cirrus_amp_driver = { .driver = { .name = "cirrus-amp", .of_match_table = cirrus_amp_of_match, }, .probe = cirrus_amp_probe, .remove = cirrus_amp_remove, }; module_platform_driver(cirrus_amp_driver); MODULE_AUTHOR("David Rhodes "); MODULE_DESCRIPTION("Cirrus Amp driver"); MODULE_LICENSE("GPL");