/* * mfc_fod.c * Samsung Mobile MFC FOD Module * * Copyright (C) 2023 Samsung Electronics * * * 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 "cps4038_fod.h" #define fod_log(str, ...) pr_info("[MFC-FOD]:%s: "str, __func__, ##__VA_ARGS__) #define DEFAULT_TX_IDX 0 #define DEFAULT_OP_MODE WPC_OP_MODE_BPP #define DEFAULT_VENDOR_ID 0x42 struct mfc_fod_op { unsigned int flag; fod_data_t *data[MFC_FOD_BAT_STATE_MAX]; }; struct mfc_fod_tx { unsigned int id; struct mfc_fod_op op[WPC_OP_MODE_MAX]; }; struct mfc_fod { struct device *parent; mfc_set_fod cb_func; struct mutex lock; union mfc_fod_state state; /* fod data */ struct mfc_fod_tx *list; unsigned int count; unsigned int ext[MFC_FOD_EXT_MAX]; /* threshold */ unsigned int bpp_vout; unsigned int cc_cv_thr; unsigned int high_swell_cc_cv_thr; unsigned int vendor_id; }; static int get_op_mode_by_str(const char *str) { if (str == NULL) return WPC_OP_MODE_NONE; if (!strncmp(str, "ppde", 4)) return WPC_OP_MODE_PPDE; if (!strncmp(str, "epp", 3)) return WPC_OP_MODE_EPP; if (!strncmp(str, "mpp", 3)) return WPC_OP_MODE_MPP; return WPC_OP_MODE_BPP; } static const char *get_ext_str(unsigned int ext_type) { switch (ext_type) { case MFC_FOD_EXT_EPP_REF_QF: return "epp_ref_qf"; case MFC_FOD_EXT_EPP_REF_RF: return "epp_ref_rf"; } return "none"; } static const char *get_bat_state_str(unsigned int bat_state) { switch (bat_state) { case MFC_FOD_BAT_STATE_CC: return "cc"; case MFC_FOD_BAT_STATE_CV: return "cv"; case MFC_FOD_BAT_STATE_FULL: return "full"; } return "none"; } static fod_data_t *mfc_fod_parse_data(struct device_node *np, int bat_state, unsigned int fod_size) { fod_data_t *data; const u32 *p; int len = 0, t_size = 0, ret; p = of_get_property(np, get_bat_state_str(bat_state), &len); if (!p) return NULL; t_size = sizeof(fod_data_t); data = kcalloc(fod_size, t_size, GFP_KERNEL); if (!data) return NULL; if (fod_size != (len / t_size)) { fod_log("not match the size(%d, %d, %d)\n", fod_size, len, t_size); goto err_size; } switch (t_size) { case sizeof(u64): ret = of_property_read_u64_array(np, get_bat_state_str(bat_state), (u64 *)data, fod_size); break; case sizeof(u32): ret = of_property_read_u32_array(np, get_bat_state_str(bat_state), (u32 *)data, fod_size); break; case sizeof(u16): ret = of_property_read_u16_array(np, get_bat_state_str(bat_state), (u16 *)data, fod_size); break; case sizeof(u8): ret = of_property_read_u8_array(np, get_bat_state_str(bat_state), (u8 *)data, fod_size); break; default: fod_log("invalid t size(%d)\n", t_size); goto err_size; } if (ret < 0) { fod_log("%s, failed to parse data (ret = %d)\n", get_bat_state_str(bat_state), ret); goto err_size; } return data; err_size: kfree(data); return NULL; } static int mfc_fod_parse_op_node(struct device_node *np, struct mfc_fod *fod, struct mfc_fod_tx *fod_tx, int op_mode, unsigned int fod_size) { struct mfc_fod_op *fod_op = &fod_tx->op[op_mode]; unsigned int flag = 0; int ret = 0, i; ret = of_property_read_u32(np, "flag", &flag); if (ret < 0) { pr_err("%s: failed to get flag of %s\n", __func__, np->name); return ret; } for (i = MFC_FOD_BAT_STATE_CC; i < MFC_FOD_BAT_STATE_MAX; i++) { switch ((flag >> (i * 4)) & 0xF) { case FOD_FLAG_ADD: fod_op->data[i] = mfc_fod_parse_data(np, i, fod_size); if (fod_op->data[i] == NULL) { ret = -1; goto err_data; } break; case FOD_FLAG_USE_CC: if (fod_op->data[MFC_FOD_BAT_STATE_CC] == NULL) { ret = -2; goto err_data; } fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_CC]; break; case FOD_FLAG_USE_CV: if (fod_op->data[MFC_FOD_BAT_STATE_CV] == NULL) { ret = -3; goto err_data; } fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_CV]; break; case FOD_FLAG_USE_FULL: if (fod_op->data[MFC_FOD_BAT_STATE_FULL] == NULL) { ret = -4; goto err_data; } fod_op->data[i] = fod_op->data[MFC_FOD_BAT_STATE_FULL]; break; case FOD_FLAG_USE_DEF_PAD: { struct mfc_fod_tx *def_tx = &fod->list[DEFAULT_TX_IDX]; if (def_tx->op[op_mode].data[i] == NULL) { ret = -5; goto err_data; } fod_op->data[i] = def_tx->op[op_mode].data[i]; } break; case FOD_FLAG_USE_DEF_OP: { struct mfc_fod_op *def_op = &fod_tx->op[DEFAULT_OP_MODE]; if (def_op->data[i] == NULL) { ret = -6; goto err_data; } fod_op->data[i] = def_op->data[i]; } break; case FOD_FLAG_NONE: default: fod_log("%s - %s is not set\n", np->name, get_bat_state_str(i)); break; } } fod_op->flag = flag; return 0; err_data: for (; i >= 0; i--) { if (((flag >> (i * 4)) & 0xF) == FOD_FLAG_ADD) kfree(fod_op->data[i]); } return ret; } static int mfc_fod_init_ext_pad(struct mfc_fod_tx *fod_tx, struct mfc_fod_tx *def_tx) { int i, j; if (fod_tx->id == DEFAULT_TX_IDX) return 0; for (j = WPC_OP_MODE_NONE; j < WPC_OP_MODE_MAX; j++) { if (def_tx->op[j].flag == 0) continue; for (i = MFC_FOD_BAT_STATE_CC; i < MFC_FOD_BAT_STATE_MAX; i++) fod_tx->op[j].data[i] = def_tx->op[j].data[i]; fod_tx->op[j].flag = (SET_FOD_CC(USE_DEF_PAD) | SET_FOD_CV(USE_DEF_PAD) | SET_FOD_FULL(USE_DEF_PAD)); } return 0; } static int mfc_fod_parse_tx_node(struct device_node *np, struct mfc_fod *fod, unsigned int fod_size) { struct device_node *tx_node = NULL; int ret = 0, tx_idx = 0; for_each_child_of_node(np, tx_node) { struct mfc_fod_tx *fod_tx = NULL; struct device_node *op_node = NULL; if (tx_idx >= fod->count) { fod_log("out of range(%d <--> %d)\n", tx_idx, fod->count); break; } fod_tx = &fod->list[tx_idx++]; if (sscanf(tx_node->name, "pad_0x%X", &fod_tx->id) < 0) { fod_log("failed to get tx id(%s)\n", tx_node->name); continue; } mfc_fod_init_ext_pad(fod_tx, &fod->list[DEFAULT_TX_IDX]); for_each_child_of_node(tx_node, op_node) { int op_mode; op_mode = get_op_mode_by_str(op_node->name); if (op_mode == WPC_OP_MODE_NONE) { fod_log("%s, invalid op name\n", op_node->name); continue; } ret = mfc_fod_parse_op_node(op_node, fod, fod_tx, op_mode, fod_size); if (ret < 0) fod_log("%s, failed to parse data(ret = %d)\n", op_node->name, ret); } } return 0; } static void mfc_fod_print_data(struct mfc_fod *fod, unsigned int fod_size) { int x, y, z, k; struct mfc_fod_tx *fod_tx; for (x = 0; x < fod->count; x++) { fod_tx = &fod->list[x]; for (y = WPC_OP_MODE_NONE + 1; y < WPC_OP_MODE_MAX; y++) { for (z = MFC_FOD_BAT_STATE_CC; z < MFC_FOD_BAT_STATE_MAX; z++) { char temp_buf[1024] = {0, }; int size = 1024; if (fod_tx->op[y].data[z] == NULL) { fod_log("PAD_0x%02X:%s:%s is null!!\n", fod_tx->id, sb_wrl_op_mode_str(y), get_bat_state_str(z)); continue; } for (k = 0; k < fod_size; k++) { snprintf(temp_buf + strlen(temp_buf), size, "0x%02X ", fod_tx->op[y].data[z][k]); size = sizeof(temp_buf) - strlen(temp_buf); } fod_log("PAD_0x%02X:%s:%s - %s\n", fod_tx->id, sb_wrl_op_mode_str(y), get_bat_state_str(z), temp_buf); } } } } static void mfc_fod_print_ext(struct mfc_fod *fod) { char ext_buf[1024] = {0, }; int x, ext_size = 1024; for (x = 0; x < MFC_FOD_EXT_MAX; x++) { snprintf(ext_buf + strlen(ext_buf), ext_size, "%02d:0x%X ", x, fod->ext[x]); ext_size = sizeof(ext_buf) - strlen(ext_buf); } fod_log("EXT - %s\n", ext_buf); } static int mfc_fod_parse_dt(struct device_node *np, struct mfc_fod *fod, unsigned int fod_size) { int ret = 0, i; np = of_find_node_by_name(np, "fod_list"); if (!np) { fod_log("fod list is null!!!!\n"); return -ENODEV; } ret = of_property_read_u32(np, "count", &fod->count); if (ret < 0) { fod_log("count is null(ret = %d)\n", ret); return ret; } fod->list = kcalloc(fod->count, sizeof(struct mfc_fod_tx), GFP_KERNEL); if (!fod->list) { fod_log("failed to alloc fod list\n"); return -ENOMEM; } ret = mfc_fod_parse_tx_node(np, fod, fod_size); if (ret < 0) { kfree(fod->list); return ret; } /* parse ext */ for (i = 0; i < MFC_FOD_EXT_MAX; i++) { ret = of_property_read_u32(np, get_ext_str(i), (unsigned int *)&fod->ext[i]); if (ret < 0) fod_log("%s is null(ret = %d)!!\n", get_ext_str(i), ret); } mfc_fod_print_data(fod, fod_size); mfc_fod_print_ext(fod); return 0; } static int mfc_fod_thr_parse_dt(struct device_node *np, struct mfc_fod *fod) { int ret = 0; ret = of_property_read_u32(np, "bpp_vout", &fod->bpp_vout); if (ret < 0) fod->bpp_vout = 7000; ret = of_property_read_u32(np, "cc_cv_thr", &fod->cc_cv_thr); if (ret < 0) fod->cc_cv_thr = 85; ret = of_property_read_u32(np, "high_swell_cc_cv_thr", &fod->high_swell_cc_cv_thr); if (ret < 0) fod->high_swell_cc_cv_thr = 70; ret = of_property_read_u32(np, "vendor_id", (unsigned int *)&fod->vendor_id); if (ret < 0) { fod_log("vendor_id is null(ret = %d)!!\n", ret); fod->vendor_id = DEFAULT_VENDOR_ID; } fod_log("bpp_vout = %d, cc_cv_thr = %d, high_swell_cc_cv_thr = %d, vendor_id = 0x%x\n", fod->bpp_vout, fod->cc_cv_thr, fod->high_swell_cc_cv_thr, fod->vendor_id); return 0; } static struct mfc_fod_tx *get_fod_tx(struct mfc_fod *fod, unsigned int tx_id) { int i; for (i = 0; i < fod->count; i++) { if (fod->list[i].id == tx_id) return &fod->list[i]; } return &fod->list[DEFAULT_TX_IDX]; } static bool check_vendor_id_to_set_op_mode_by_vout(union mfc_fod_state *state, int vendor_id) { return (state->vendor_id == vendor_id); } static int check_op_mode_vout(struct mfc_fod *fod, union mfc_fod_state *state) { if (!check_vendor_id_to_set_op_mode_by_vout(state, fod->vendor_id)) return state->op_mode; switch (state->op_mode) { case WPC_OP_MODE_EPP: case WPC_OP_MODE_PPDE: if (state->vout <= fod->bpp_vout) return WPC_OP_MODE_BPP; break; case WPC_OP_MODE_MPP: break; default: /* default op mode */ return WPC_OP_MODE_BPP; } return state->op_mode; } static fod_data_t *mfc_fod_get_data(struct mfc_fod *fod) { union mfc_fod_state *fod_state = &fod->state; struct mfc_fod_tx *fod_tx; if (fod->count <= 0) return NULL; fod_tx = get_fod_tx(fod, fod_state->tx_id); fod_state->fake_op_mode = check_op_mode_vout(fod, fod_state); return fod_tx->op[fod_state->fake_op_mode].data[fod_state->bat_state]; } struct mfc_fod *mfc_fod_init(struct device *dev, unsigned int fod_size, mfc_set_fod cb_func) { struct mfc_fod *fod; int ret = 0; if (IS_ERR_OR_NULL(dev) || (fod_size <= 0) || (fod_size > MFC_FOD_MAX_SIZE) || (cb_func == NULL)) return ERR_PTR(-EINVAL); fod = kzalloc(sizeof(struct mfc_fod), GFP_KERNEL); if (!fod) return ERR_PTR(-ENOMEM); ret = mfc_fod_parse_dt(dev->of_node, fod, fod_size); if (ret < 0) { kfree(fod); return ERR_PTR(ret); } mfc_fod_thr_parse_dt(dev->of_node, fod); mutex_init(&fod->lock); fod->parent = dev; fod->cb_func = cb_func; fod->state.value = 0; fod_log("DONE!!\n"); return fod; } EXPORT_SYMBOL(mfc_fod_init); int mfc_fod_init_state(struct mfc_fod *fod) { if (IS_ERR(fod)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.value != 0) { fod->state.value = 0; fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_init_state); int mfc_fod_refresh(struct mfc_fod *fod) { if (IS_ERR(fod)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.value != 0) fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_refresh); int mfc_fod_set_op_mode(struct mfc_fod *fod, int op_mode) { if (IS_ERR(fod) || (op_mode < WPC_OP_MODE_NONE) || (op_mode >= WPC_OP_MODE_MAX)) return -EINVAL; mutex_lock(&fod->lock); switch (fod->state.op_mode) { case WPC_OP_MODE_EPP: case WPC_OP_MODE_MPP: case WPC_OP_MODE_PPDE: fod_log("prevent op mode(%d)!!\n", op_mode); break; case WPC_OP_MODE_BPP: case WPC_OP_MODE_NONE: default: if (fod->state.op_mode != op_mode) { fod->state.op_mode = op_mode; fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } break; } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_op_mode); int mfc_fod_set_vendor_id(struct mfc_fod *fod, int vendor_id) { if (IS_ERR(fod) || (vendor_id < 0) || (vendor_id > 0xFF)) return -EINVAL; mutex_lock(&fod->lock); if (vendor_id != fod->state.vendor_id) { fod->state.vendor_id = vendor_id; fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_vendor_id); int mfc_fod_set_tx_id(struct mfc_fod *fod, int tx_id) { if (IS_ERR(fod) || (tx_id < 0) || (tx_id >= 256)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.tx_id != tx_id) { fod->state.tx_id = tx_id; fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_tx_id); static int check_bat_state(struct mfc_fod *fod) { if (fod->state.high_swell) return (fod->state.bat_cap > fod->high_swell_cc_cv_thr) ? MFC_FOD_BAT_STATE_CV : MFC_FOD_BAT_STATE_CC; return (fod->state.bat_cap > fod->cc_cv_thr) ? MFC_FOD_BAT_STATE_CV : MFC_FOD_BAT_STATE_CC; } static int set_bat_state(struct mfc_fod *fod, int bat_state) { switch (fod->state.bat_state) { case MFC_FOD_BAT_STATE_FULL: fod_log("prevent bat state(%d)!!\n", bat_state); break; case MFC_FOD_BAT_STATE_CC: case MFC_FOD_BAT_STATE_CV: default: if (fod->state.bat_state != bat_state) { fod->state.bat_state = bat_state; fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } break; } return 0; } int mfc_fod_set_bat_state(struct mfc_fod *fod, int bat_state) { if (IS_ERR(fod) || (bat_state < MFC_FOD_BAT_STATE_CC) || (bat_state >= MFC_FOD_BAT_STATE_MAX)) return -EINVAL; mutex_lock(&fod->lock); set_bat_state(fod, bat_state); mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_bat_state); int mfc_fod_set_bat_cap(struct mfc_fod *fod, int bat_cap) { if (IS_ERR(fod) || (bat_cap < 0) || (bat_cap > 100)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.bat_cap != bat_cap) { fod->state.bat_cap = bat_cap; set_bat_state(fod, check_bat_state(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_bat_cap); int mfc_fod_set_vout(struct mfc_fod *fod, int vout) { if (IS_ERR(fod) || (vout <= 0)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.vout != vout) { int new_op_mode, old_op_mode; old_op_mode = check_op_mode_vout(fod, &fod->state); fod->state.vout = vout; new_op_mode = check_op_mode_vout(fod, &fod->state); if (new_op_mode != old_op_mode) fod->cb_func(fod->parent, &fod->state, mfc_fod_get_data(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_vout); int mfc_fod_set_high_swell(struct mfc_fod *fod, bool state) { if (IS_ERR(fod)) return -EINVAL; mutex_lock(&fod->lock); if (fod->state.high_swell != state) { fod->state.high_swell = state; set_bat_state(fod, check_bat_state(fod)); } mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_set_high_swell); int mfc_fod_get_state(struct mfc_fod *fod, union mfc_fod_state *state) { if (IS_ERR(fod) || (state == NULL)) return -EINVAL; mutex_lock(&fod->lock); state->value = fod->state.value; mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_get_state); int mfc_fod_get_ext(struct mfc_fod *fod, int ext_type, int *data) { if (IS_ERR(fod) || (ext_type < MFC_FOD_EXT_EPP_REF_QF) || (ext_type >= MFC_FOD_EXT_MAX) || (data == NULL)) return -EINVAL; mutex_lock(&fod->lock); *data = fod->ext[ext_type]; mutex_unlock(&fod->lock); return 0; } EXPORT_SYMBOL(mfc_fod_get_ext);